程序开发之道

一个专注技术的程序员

作为 JavaScript/Node.js 开发者,发布自己的 NPM 包是技术成长的重要一步。本文将从零开始,详细介绍如何开发、打包、测试并发布一个 NPM 包。

为什么需要发布 NPM 包?

  • 代码复用:将常用工具封装成包,多项目共享
  • 开源贡献:分享你的解决方案,帮助其他开发者
  • 技术积累:规范化代码组织,提升工程质量
  • 简历加分:维护公开项目,展示技术能力

目录结构规范

一个标准的 NPM 包项目结构:

1
2
3
4
5
6
7
8
9
10
11
12
my-package/
├── src/ # 源代码目录
│ ├── index.js # 主入口文件
│ └── utils.js # 辅助模块
├── dist/ # 构建产物(可选)
├── test/ # 测试文件
├── package.json # 包配置文件
├── README.md # 说明文档
├── LICENSE # 许可证文件
├── .gitignore # Git忽略配置
├── .npmignore # NPM忽略配置(可选)
└── CHANGELOG.md # 变更日志(推荐)

package.json 配置详解

这是包的核心配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{
"name": "my-awesome-package",
"version": "1.0.0",
"description": "一个实用的工具库",
"main": "src/index.js",
"module": "dist/index.esm.js",
"types": "dist/index.d.ts",
"files": [
"src",
"dist"
],
"scripts": {
"test": "jest",
"build": "rollup -c",
"prepublishOnly": "npm run build && npm run test"
},
"keywords": [
"utility",
"tools",
"helper"
],
"author": "FoleyDang",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/foleydang/my-package.git"
},
"bugs": {
"url": "https://github.com/foleydang/my-package/issues"
},
"homepage": "https://github.com/foleydang/my-package#readme",
"dependencies": {},
"devDependencies": {
"jest": "^29.0.0",
"rollup": "^3.0.0"
},
"engines": {
"node": ">=14.0.0"
}
}

关键字段说明

字段 说明
name 包名,必须唯一,建议使用小写+连字符
version 版本号,遵循 SemVer 规范
main CommonJS 入口
module ES Module 入口
types TypeScript 类型声明文件
files 发布时包含的文件/目录
exports 现代包导出配置(推荐)

exports 配置(推荐)

现代 NPM 包推荐使用 exports 字段:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"exports": {
".": {
"import": "./dist/index.esm.js",
"require": "./src/index.js",
"types": "./dist/index.d.ts"
},
"./utils": {
"import": "./dist/utils.esm.js",
"require": "./src/utils.js"
}
}
}

版本号规范(SemVer)

版本号格式:主版本.次版本.补丁版本(如 1.2.3

  • 主版本(Major):不兼容的 API 变化
  • 次版本(Minor):向后兼容的功能新增
  • 补丁版本(Patch):向后兼容的问题修复

常用命令:

1
2
3
4
5
6
7
8
9
10
11
# 补丁版本(1.0.0 -> 1.0.1)
npm version patch

# 次版本(1.0.0 -> 1.1.0)
npm version minor

# 主版本(1.0.0 -> 2.0.0)
npm version major

# 预发布版本
npm version prerelease --preid=beta # 1.0.0 -> 1.0.1-beta.0

包代码开发

入口文件示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// src/index.js

/**
* 格式化日期
* @param {Date|string|number} date 日期对象/字符串/时间戳
* @param {string} format 格式模板
* @returns {string} 格式化后的日期字符串
*/
function formatDate(date, format = 'YYYY-MM-DD') {
const d = new Date(date);
const map = {
YYYY: d.getFullYear(),
MM: String(d.getMonth() + 1).padStart(2, '0'),
DD: String(d.getDate()).padStart(2, '0'),
HH: String(d.getHours()).padStart(2, '0'),
mm: String(d.getMinutes()).padStart(2, '0'),
ss: String(d.getSeconds()).padStart(2, '0')
};
return format.replace(/YYYY|MM|DD|HH|mm|ss/g, match => map[match]);
}

/**
* 深度克隆对象
* @param {*} obj 要克隆的对象
* @returns {*} 克隆后的对象
*/
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(item => deepClone(item));
}
const cloned = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}

module.exports = {
formatDate,
deepClone
};

ES Module 版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// dist/index.esm.js

export function formatDate(date, format = 'YYYY-MM-DD') {
const d = new Date(date);
const map = {
YYYY: d.getFullYear(),
MM: String(d.getMonth() + 1).padStart(2, '0'),
DD: String(d.getDate()).padStart(2, '0'),
HH: String(d.getHours()).padStart(2, '0'),
mm: String(d.getMinutes()).padStart(2, '0'),
ss: String(d.getSeconds()).padStart(2, '0')
};
return format.replace(/YYYY|MM|DD|HH|mm|ss/g, match => map[match]);
}

export function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
if (Array.isArray(obj)) {
return obj.map(item => deepClone(item));
}
const cloned = {};
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
cloned[key] = deepClone(obj[key]);
}
}
return cloned;
}

使用 Rollup 构建

Rollup 是打包库的最佳选择,输出干净、高效。

安装依赖

1
npm install --save-dev rollup @rollup/plugin-node-resolve @rollup/plugin-commonjs @rollup/plugin-terser

rollup.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import terser from '@rollup/plugin-terser';

export default [
// ES Module 输出
{
input: 'src/index.js',
output: {
file: 'dist/index.esm.js',
format: 'esm',
sourcemap: true
},
plugins: [resolve(), commonjs()]
},
// CommonJS 输出
{
input: 'src/index.js',
output: {
file: 'dist/index.cjs.js',
format: 'cjs',
sourcemap: true
},
plugins: [resolve(), commonjs()]
},
// UMD 输出(浏览器兼容)
{
input: 'src/index.js',
output: {
file: 'dist/index.umd.js',
format: 'umd',
name: 'MyPackage',
sourcemap: true,
plugins: [terser()]
},
plugins: [resolve(), commonjs()]
}
];

编写测试

使用 Jest 编写单元测试:

1
npm install --save-dev jest

test/index.test.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
const { formatDate, deepClone } = require('../src/index');

describe('formatDate', () => {
test('格式化默认格式', () => {
const date = new Date('2026-05-09');
expect(formatDate(date)).toBe('2026-05-09');
});

test('自定义格式', () => {
const date = new Date('2026-05-09 11:30:00');
expect(formatDate(date, 'YYYY/MM/DD HH:mm:ss')).toBe('2026/05/09 11:30:00');
});

test('时间戳输入', () => {
const timestamp = 1746662400000; // 2026-05-09 00:00:00 UTC
expect(formatDate(timestamp, 'YYYY-MM-DD')).toBe('2026-05-09');
});
});

describe('deepClone', () => {
test('克隆简单对象', () => {
const obj = { a: 1, b: 'test' };
const cloned = deepClone(obj);
expect(cloned).toEqual(obj);
expect(cloned).not.toBe(obj);
});

test('克隆嵌套对象', () => {
const obj = { a: { b: { c: 1 } } };
const cloned = deepClone(obj);
cloned.a.b.c = 2;
expect(obj.a.b.c).toBe(1);
});

test('克隆数组', () => {
const arr = [1, { a: 2 }, [3, 4]];
const cloned = deepClone(arr);
expect(cloned).toEqual(arr);
cloned[1].a = 3;
expect(arr[1].a).toBe(2);
});
});

README.md 编写规范

README 是包的门面,必须清晰完整:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# my-awesome-package

> 一个实用的 JavaScript 工具库

## 安装

\`\`\`bash
npm install my-awesome-package
# 或
yarn add my-awesome-package
# 或
pnpm add my-awesome-package
\`\`\`

## 使用

\`\`\`javascript
// CommonJS
const { formatDate, deepClone } = require('my-awesome-package');

// ES Module
import { formatDate, deepClone } from 'my-awesome-package';

// 格式化日期
formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss');
// 输出: '2026-05-09 11:30:00'

// 深度克隆
const cloned = deepClone({ a: { b: 1 } });
\`\`\`

## API 文档

### formatDate(date, format)

| 参数 | 类型 | 说明 |
|------|------|------|
| date | Date/string/number | 日期对象、字符串或时间戳 |
| format | string | 格式模板,默认 'YYYY-MM-DD' |

### deepClone(obj)

| 参数 | 类型 | 说明 |
|------|------|------|
| obj | any | 要克隆的对象 |

## 许可证

MIT

NPM 注册与登录

1. 注册账号

1
2
3
# 在官网注册:https://www.npmjs.com/signup
# 或使用命令行
npm adduser

按提示输入用户名、密码、邮箱,完成邮箱验证。

2. 登录验证

1
2
3
4
npm login

# 验证登录状态
npm whoami

3. 配置 2FA(推荐)

在 npmjs.com 站点设置页面启用双因素认证,保护账号安全。

本地测试发布

发布前先本地测试:

1
2
3
4
5
6
7
8
# 在包目录
npm link

# 在测试项目目录
npm link my-awesome-package

# 现在可以在测试项目中引用
const pkg = require('my-awesome-package');

使用 npm pack

1
2
3
4
5
# 打包成 .tgz 文件
npm pack

# 在测试项目中安装
npm install ./my-awesome-package-1.0.0.tgz

正式发布

发布流程

1
2
3
4
5
6
7
8
9
10
11
12
13
# 1. 确保代码已提交
git status

# 2. 运行测试
npm test

# 3. 构建(如有)
npm run build

# 4. 发布
npm publish

# 发布成功后会收到邮件通知

发布特定标签

1
2
3
4
5
6
7
8
# 发布 beta 版本
npm publish --tag beta

# 发布 next 版本
npm publish --tag next

# 用户安装指定标签
npm install my-awesome-package@beta

发布 scoped 包

scoped 包(如 @username/package)默认为私有包,需要付费。

发布公开 scoped 包:

1
npm publish --access public

常见问题处理

包名已存在

  • 换一个包名
  • 使用 scoped 包:@yourname/package-name
  • 检查是否拼写错误

发布失败

1
2
3
4
5
6
7
8
# 检查登录状态
npm whoami

# 检查包配置
npm publish --dry-run

# 查看将要发布的文件
npm pack --dry-run

更新已发布的包

1
2
3
4
5
6
# 1. 修改代码
# 2. 更新版本号
npm version patch/minor/major

# 3. 发布
npm publish

删除已发布的包

1
2
3
4
5
6
7
8
# 撤销 24 小时内发布的版本
npm unpublish my-awesome-package@1.0.0

# 撤销整个包(谨慎操作)
npm unpublish my-awesome-package --force

# 弃用包(推荐,不删除)
npm deprecate my-awesome-package "此包已弃用,请使用 xxx 替代"

最佳实践

  1. 语义化版本:严格遵循 SemVer 规范
  2. 完善的测试:发布前确保测试通过
  3. 清晰的文档:README 必须包含安装、使用、API 说明
  4. 变更日志:维护 CHANGELOG.md 记录版本变化
  5. 类型声明:提供 TypeScript 类型文件(.d.ts)
  6. 持续集成:使用 GitHub Actions 自动测试发布

GitHub Actions 自动发布

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# .github/workflows/publish.yml
name: Publish to NPM

on:
push:
tags:
- 'v*'

jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm test
- run: npm run build
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

在 GitHub 项目设置 → Secrets 中添加 NPM_TOKEN(从 npmjs.com 获取)。

总结

发布 NPM 包的完整流程:

  1. 规划包的功能和结构
  2. 编写代码和测试
  3. 配置 package.json
  4. 编写 README 和 LICENSE
  5. 本地测试(npm link / npm pack)
  6. 注册登录 NPM
  7. 发布(npm publish)
  8. 维护更新(版本管理)

掌握了这些,你就可以将自己的代码分享给全世界了!

参考资料

WireGuard 是一个现代化、高性能的 VPN 协议,相比 OpenVPN 更轻量、更快速。本文记录了在阿里云 ECS 上部署 WireGuard VPN 服务器的完整过程,包括服务器端配置、客户端配置、防火墙设置以及常见问题排查。

为什么选择 WireGuard?

WireGuard 相比传统 VPN 方案的优势:

  • 性能优异:内核级实现,延迟更低
  • 配置简洁:无需复杂的证书管理,只需公钥/私钥配对
  • 安全性高:使用现代加密协议(ChaCha20、Poly1305 等)
  • 跨平台:支持 Linux、Windows、macOS、iOS、Android

服务器端部署

1. 安装 WireGuard

1
2
3
4
5
# CentOS/RHEL
yum install wireguard-tools

# Ubuntu/Debian
apt install wireguard

2. 生成密钥对

WireGuard 使用公钥加密来验证身份,每个参与者都需要一对密钥(私钥 + 公钥)。

为什么需要两对密钥?

打个比方:

  • 服务器就像一个「门」,它有自己的钥匙(私钥)和门牌号(公钥)
  • 客户端就像一个「人」,有自己的身份证(私钥)和证件号(公钥)

要进入这扇门:

  1. 人需要知道门的公钥(门牌号),才能找到正确的门
  2. 门需要知道人的公钥(证件号),才能验证是不是允许这个人进来

这就是为什么双方都需要互相交换公钥

密钥 保存在哪里 作用
服务器私钥 服务器配置 [Interface] 服务器身份验证,绝不外泄
服务器公钥 客户端配置 [Peer] 客户端用它找到服务器
客户端私钥 客户端配置 [Interface] 客户端身份验证,绝不外泄
客户端公钥 服务器配置 [Peer] 服务器用它验证客户端身份

密钥流向图

1
2
3
4
5
6
7
8
9
服务器配置文件                     客户端配置文件
┌─────────────────────┐ ┌─────────────────────┐
│ [Interface] │ │ [Interface] │
│ PrivateKey = 服务器私钥 │ │ PrivateKey = 客户端私钥 │
│ │ │ │
│ [Peer] │ │ [Peer] │
│ PublicKey = 客户端公钥 │◄──交换──►│ PublicKey = 服务器公钥 │
│ AllowedIPs = ... │ │ Endpoint = ... │
└─────────────────────┘ └─────────────────────┘

记住这个原则

  • 自己的私钥 → 写在自己的 [Interface]
  • 对方的公钥 → 写在自己的 [Peer]

生成密钥

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 创建配置目录
mkdir -p /etc/wireguard
cd /etc/wireguard

# 生成服务器密钥对
wg genkey | tee server_private.key | wg pubkey > server_public.key

# 生成客户端密钥对
wg genkey | tee client_private.key | wg pubkey > client_public.key

# 查看生成的密钥
cat server_private.key # 服务器私钥 → 写入服务器 [Interface]
cat server_public.key # 服务器公钥 → 写入客户端 [Peer]
cat client_private.key # 客户端私钥 → 写入客户端 [Interface]
cat client_public.key # 客户端公钥 → 写入服务器 [Peer]

重要:私钥绝对不能泄露!公钥可以公开分享给对方。

3. 创建服务器配置文件

1
2
3
4
5
6
7
8
9
10
cat > /etc/wireguard/wg0.conf << EOF
[Interface]
PrivateKey = <服务器私钥>
Address = 10.100.0.1/24
ListenPort = 443

[Peer]
PublicKey = <客户端公钥>
AllowedIPs = 10.100.0.2/32
EOF

配置说明:

  • ListenPort = 443:使用 HTTPS 端口,避免运营商封锁非标准端口(如 51820)
  • Address = 10.100.0.1/24:VPN 内网地址段,服务器使用 .1
  • AllowedIPs = 10.100.0.2/32:只允许该客户端使用 .2 地址

4. 启动 WireGuard 服务

1
2
3
4
5
6
# 启动服务
wg-quick up wg0

# 或使用 systemd 管理
systemctl start wg-quick@wg0
systemctl enable wg-quick@wg0 # 开机自启

验证服务状态:

1
wg show

输出示例:

1
2
3
4
5
6
7
interface: wg0
public key: <服务器公钥>
private key: (hidden)
listening port: 443

peer: <客户端公钥>
allowed ips: 10.100.0.2/32

防火墙与 NAT 配置

1. 开放防火墙端口

1
2
3
4
5
6
# firewalld
firewall-cmd --permanent --add-port=443/udp
firewall-cmd --reload

# iptables
iptables -A INPUT -p udp --dport 443 -j ACCEPT

2. 配置 NAT 规则(让客户端能上网)

WireGuard 客户端通过 VPN 连接后,需要 NAT 规则才能访问外网:

1
2
3
4
5
6
# 使用 firewalld 持久化 NAT 规则
firewall-cmd --permanent --direct --add-rule ipv4 nat POSTROUTING 0 -s 10.100.0.0/24 -o eth0 -j MASQUERADE
firewall-cmd --reload

# 或使用 iptables(重启后会丢失)
iptables -t nat -A POSTROUTING -s 10.100.0.0/24 -o eth0 -j MASQUERADE

3. 开启 IP 转发

1
2
3
4
5
6
7
8
9
# 检查是否已开启
cat /proc/sys/net/ipv4/ip_forward

# 如果为 0,需要开启
sysctl -w net.ipv4.ip_forward=1

# 持久化设置
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
sysctl -p

4. FORWARD 规则持久化

1
2
3
firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 0 -i wg0 -o eth0 -j ACCEPT
firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 0 -i eth0 -o wg0 -m state --state ESTABLISHED,RELATED -j ACCEPT
firewall-cmd --reload

5. 阿里云安全组配置

在阿里云控制台添加安全组规则:

方向 协议 端口 来源
入站 UDP 443 0.0.0.0/0
出站 UDP 全部 0.0.0.0/0

客户端配置

配置文件内容

1
2
3
4
5
6
7
8
9
10
[Interface]
PrivateKey = <客户端私钥>
Address = 10.100.0.2/24
DNS = 8.8.8.8

[Peer]
PublicKey = <服务器公钥>
Endpoint = <服务器公网IP>:443
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

配置说明:

  • AllowedIPs = 0.0.0.0/0:所有流量都走 VPN
  • PersistentKeepalive = 25:保持连接活跃,避免断开

生成二维码(方便手机导入)

1
2
3
4
5
6
7
8
# 安装 qrencode
yum install qrencode

# 生成二维码
qrencode -t PNG -o wireguard-qr.png -r client.conf

# 或直接显示在终端
qrencode -t ANSIUTF8 -r client.conf

各平台客户端下载

平台 下载方式
iOS App Store 搜索 “WireGuard”
Android Google Play 或 F-Droid
Windows https://www.wireguard.com/install/
macOS App Store 或 brew install wireguard-tools

常见问题排查

1. 能连接但无法上网

检查 NAT 规则:

1
iptables -t nat -L POSTROUTING -n -v

如果没有 VPN 网段的 MASQUERADE 规则:

1
iptables -t nat -A POSTROUTING -s 10.100.0.0/24 -o eth0 -j MASQUERADE

2. 握手失败(无 latest handshake)

检查密钥是否匹配:

1
2
3
4
5
# 服务器端:客户端公钥应该和配置文件一致
wg show | grep peer

# 客户端配置里的服务器公钥应该和服务器实际公钥一致
cat /etc/wireguard/wg0.conf | grep PrivateKey | awk '{print $3}' | wg pubkey

3. 数据包发送但无响应

可能是端口被封锁。尝试:

  • 改用 UDP 443(HTTPS 端口)
  • 检查阿里云安全组是否开放 UDP 出站

4. 连接后流量不走 VPN

检查客户端 AllowedIPs:

1
2
AllowedIPs = 0.0.0.0/0  # 全局代理
AllowedIPs = 10.100.0.0/24 # 只代理 VPN 内网

5. 规则重启后丢失

确保使用 firewalld 持久化:

1
2
# 检查持久化规则
firewall-cmd --permanent --direct --get-all-rules

服务管理命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 启动
wg-quick up wg0
systemctl start wg-quick@wg0

# 停止
wg-quick down wg0
systemctl stop wg-quick@wg0

# 状态
wg show
systemctl status wg-quick@wg0

# 重启
systemctl restart wg-quick@wg0

添加更多客户端

每个客户端需要独立的密钥对和 IP 地址:

1
2
3
4
5
6
7
8
# 生成新客户端密钥
wg genkey | tee client2_private.key | wg pubkey > client2_public.key

# 在服务器端添加 peer
wg set wg0 peer <客户端2公钥> allowed-ips 10.100.0.3/32

# 更新服务器配置文件(持久化)
# 在 /etc/wireguard/wg0.conf 添加新的 [Peer] 块

小结

WireGuard 的部署比 OpenVPN 简单得多,核心要点:

  1. 密钥匹配:服务器和客户端的公钥/私钥必须正确配对
  2. 端口选择:用 UDP 443 避免运营商封锁
  3. NAT 规则:确保 VPN 网段有 MASQUERADE
  4. 规则持久化:用 firewalld direct 规则或 iptables-save

配置完成后,重启服务会自动恢复,无需手动干预。

作为一名程序员,终端是日常工作中最频繁使用的工具之一。一个美观、高效的终端环境不仅能提升工作效率,还能让编程体验更加愉悦。本文将介绍如何使用 Starship 打造一个现代化、跨平台的终端提示符。

什么是 Starship?

Starship 是一个轻量、快速、跨 shell 的现代化提示符工具:

  • 跨平台:支持 macOS、Linux、Windows
  • 跨 Shell:支持 Bash、Zsh、Fish、PowerShell 等
  • 高度可定制:丰富的配置选项
  • 性能优异:使用 Rust 编写,启动速度极快

安装前置条件

安装一个 Nerd Font 字体,否则图标可能显示为乱码:

1
2
3
# 推荐 MesloLGS NF 字体
brew tap homebrew/cask-fonts
brew install --cask font-meslo-lg-nerd-font

安装后在终端设置中将字体改为 MesloLGS NF

安装 Starship

1
2
3
4
5
# macOS
brew install starship

# Linux
curl -sS https://starship.rs/install.sh | sh

配置 Shell

根据你使用的 Shell 添加初始化命令:

1
2
3
4
5
6
7
# Zsh
echo 'eval "$(starship init zsh)"' >> ~/.zshrc
source ~/.zshrc

# Bash
echo 'eval "$(starship init bash)"' >> ~/.bashrc
source ~/.bashrc

创建配置文件

1
2
mkdir -p ~/.config
touch ~/.config/starship.toml

推荐配置(青色背景风格)

一个简洁美观的配置效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# ~/.config/starship.toml

format = """[$username@$hostname$directory$git_branch$git_status](bg:cyan fg:black) $character"""
add_newline = false

[username]
show_always = true
format = "$user"

[hostname]
ssh_only = false
format = "$hostname"

[directory]
format = ">$path"
truncation_length = 3

[git_branch]
format = ">$branch"

[git_status]
format = "$all_status"

[character]
success_symbol = "[❯](bold cyan)"
error_symbol = "[❯](bold red)"

效果展示:

1
foleydang@hostname>workspace>master ❯

Powerline 风格配置

如果你喜欢更丰富的效果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
format = """
[](#9A348E)$directory\
[](fg:#9A348E bg:#DA627D)$git_branch$git_status\
[](fg:#DA627D bg:#FCA17D)$nodejs$python$rust\
[](fg:#FCA17D bg:#86BBD8)$time\
[ ](fg:#86BBD8)\
$line_break$character"""

[directory]
style = "bg:#9A348E"
truncation_length = 3
truncation_symbol = "…/"

[git_branch]
symbol = " "
style = "bg:#DA627D"
format = '[$symbol$branch ]($style)'

[git_status]
style = "bg:#DA627D"
format = '[$all_status$ahead_behind ]($style)'

[nodejs]
symbol = " "
style = "bg:#FCA17D"
format = '[$symbol($version )]($style)'

[python]
symbol = " "
style = "bg:#FCA17D"
format = '[$symbol($version )]($style)'

[time]
disabled = false
time_format = "%R"
style = "bg:#86BBD8"
format = '[$time ]($style)'

[character]
success_symbol = "[➜](bold green)"
error_symbol = "[➜](bold red)"

一键安装脚本

保存为 install-starship.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/bin/bash
# Starship 一键安装脚本

# 安装 Starship
if command -v brew &> /dev/null; then
brew install starship
elif command -v curl &> /dev/null; then
curl -sS https://starship.rs/install.sh | sh
else
echo "需要 Homebrew 或 curl 来安装 Starship"
exit 1
fi

# 创建配置文件
mkdir -p ~/.config
cat > ~/.config/starship.toml << 'STARSHIP_EOF'
format = """[$username@$hostname$directory$git_branch$git_status](bg:cyan fg:black) $character"""
add_newline = false

[username]
show_always = true
format = "$user"

[hostname]
ssh_only = false
format = "$hostname"

[directory]
format = ">$path"
truncation_length = 3

[git_branch]
format = ">$branch"

[git_status]
format = "$all_status"

[character]
success_symbol = "[❯](bold cyan)"
error_symbol = "[❯](bold red)"
STARSHIP_EOF

# 添加到 shell 配置
if [ -f ~/.zshrc ]; then
grep -q "starship init zsh" ~/.zshrc || echo 'eval "$(starship init zsh)"' >> ~/.zshrc
echo "✅ 已添加到 ~/.zshrc"
elif [ -f ~/.bashrc ]; then
grep -q "starship init bash" ~/.bashrc || echo 'eval "$(starship init bash)"' >> ~/.bashrc
echo "✅ 已添加到 ~/.bashrc"
fi

echo "✅ Starship 配置完成"
echo "运行 source ~/.zshrc 或 source ~/.bashrc 使配置生效"

运行:

1
2
chmod +x install-starship.sh
./install-starship.sh

远程主机迁移

复制配置到远程主机

1
2
3
4
5
6
# 复制配置文件
scp ~/.config/starship.toml user@remote:~/.config/

# 在远程主机安装
ssh user@remote "curl -sS https://starship.rs/install.sh | sh"
ssh user@remote "echo 'eval \"\$(starship init zsh)\"' >> ~/.zshrc"

使用一键脚本

1
scp install-starship.sh user@remote:~/ssh user@remote "chmod +x ~/install-starship.sh && ~/install-starship.sh"

自定义颜色

修改 bg:cyan fg:black 可以更换配色:

可用颜色:

  • black, white, red, green, yellow, blue, magenta, cyan
  • 或使用 hex:bg:#1e1e2e fg:#cdd6f4

示例(紫色背景):

1
format = """[$username@$hostname$directory](bg:magenta fg:white) $character"""

配合 iTerm2 使用

进一步优化终端体验:

  1. 设置字体:Preferences → Profiles → Text → Font → MesloLGS NF
  2. 启用真彩色:Preferences → Profiles → Terminal → Enable True Color
  3. 推荐配色:Dracula 或 Snazzy 主题

常见问题

图标显示为乱码

确保终端字体设置为 Nerd Font。

启动速度慢

减少不必要的模块可以提升启动速度。

颜色不显示

  • 不要手动设置 TERM 环境变量
  • 某些终端(如 basic TUI)不支持颜色

配置文件位置

文件 位置
Starship 配置 ~/.config/starship.toml
Zsh 初始化 ~/.zshrc
Bash 初始化 ~/.bashrc

总结

Starship 让终端焕然一新,推荐配置组合:

  • 终端:iTerm2
  • Shell:Zsh
  • 提示符:Starship
  • 字体:MesloLGS NF
  • 配色:青色背景 + 黑色字体

Happy Coding!


参考:Starship 官方文档

使用 Hexo 搭建博客,且采用 NexT 主题,详细介绍写一篇新博客的步骤。

创建新博客文章

在终端里,切换到博客根目录(也就是 foleydang 目录),然后运行下面的命令来创建新的博客文章:

1
2
cd /Users/bytedance/foleydang
hexo new "你的文章标题"

运行该命令后,Hexo 会在 source/_posts 目录下生成一个新的 Markdown 文件,文件名是你设置的文章标题。例如,创建名为 “新博客文章” 的文章:

编辑博客文章

使用文本编辑器(像 Visual Studio Code、Sublime Text 等)打开刚生成的 Markdown 文件,文件开头会有类似下面的 Front-Matter 信息:

1
2
3
4
---
title: 新博客文章
tags:
---

你可以按照需求补充或修改这些信息,比如添加 date(文章发布日期)、categories(文章分类)、tags(文章标签)等:

1
2
3
4
5
6
7
8
9
---
title: 新博客文章
date: 2025-07-16
categories:
- 技术
tags:
- Hexo
- 博客
---

接着在 Front-Matter 之后撰写文章内容,Markdown 语法支持标题、列表、图片、链接等常见格式。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
---
title: 新博客文章
date: 2025-07-16
categories:
- 技术
tags:
- Hexo
- 博客
---

## 引言
这是我的第一篇使用 Hexo 搭建的博客文章。

### 搭建过程
在搭建博客时,我使用了 Hexo 这个静态网站生成器,并且选择了 NexT 主题。具体步骤如下:
1. 安装 Hexo
2. 创建新的 Hexo 项目
3. 安装 NexT 主题
4. 配置博客信息

### 总结
通过这次搭建博客的经历,我对静态网站生成器有了更深入的了解。

本地预览文章

在终端运行以下命令生成静态文件并启动本地服务器:

1
2
hexo generate
hexo server

或者使用简写命令:

1
2
hexo g
hexo s

启动成功后,在浏览器访问 http://localhost:4000 就能预览新写的博客文章。

部署博客到远程仓库

若要将博客部署到 GitHub Pages 等远程仓库,需先在 _config.yml 文件里配置部署信息,当前配置如下:

1
2
3
4
5
# _config.yml
deploy:
type: git
repo: git@github.com:foleydang/foleydang.github.io.git
branch: master

配置完成后,在终端运行以下命令部署博客:

1
hexo deploy

或者使用简写命令:

1
hexo d

部署成功后,访问 https://foleydang.github.io 就能看到新发布的博客文章。

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

0%