Deprecated🛑❤️🩹👶
merge into https://github.com/AAStarCommunity/AAStar_SDK
AAStar npm packages collection: You can use
pnpm install @aastar/airaccount
@aastar/superpaymaster
@aastar/cometens
@aastar/openpnts
@aastar/opencards
@aastar/arcadia
@aastar/cos72
or
pnpm install aastar
Why do you install this package?
Provide a moveable\self-custody\crypto account with life cycle service. Permissionless. Trustless. Decentralized. In 3 steps: bind, send, claim, move(recover)
Embbeded into AirAccount, but provide ERC20 gas token ability for any community individually.
Embbeded into AirAccount, but provide set your own ENS name for your community individually.
# 安装 Node.js (推荐使用 nvm 管理 Node.js 版本)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
nvm install node # 安装最新版
nvm install --lts # 或安装长期支持版
# 确认安装
node --version
npm --version
# 安装一些全局工具
npm install -g typescript ts-node np
- 访问 npm 官网 并注册
- 在命令行登录 npm (后续发布时需要)
npm login
在开始编码前,确定以下内容:
- SDK 的目标和用途
- 核心功能和 API 设计
- 依赖关系
- 支持的平台 (浏览器、Node.js、React Native 等)
- 命名规范 (包名必须唯一)
- 核心 API 结构
# 创建项目目录 (使用你希望的包名)
mkdir my-awesome-sdk
cd my-awesome-sdk
npm init
按照提示填写信息:
name
: 包名 (可以使用@组织名/包名
格式)version
: 版本号 (推荐从 0.1.0 开始)description
: 简短描述main
: 入口文件 (通常是dist/index.js
)scripts
: 构建脚本repository
: 代码仓库地址keywords
: 关键词,帮助用户发现你的包author
: 作者信息license
: 许可证 (如 MIT)
git init
echo "node_modules\ndist\n.env\n*.log" > .gitignore
mkdir src tests examples docs
touch src/index.ts README.md LICENSE
# 安装 TypeScript
npm install typescript --save-dev
# 初始化 TypeScript 配置
npx tsc --init
编辑 tsconfig.json
:
{
"compilerOptions": {
"target": "es2018",
"module": "commonjs",
"declaration": true,
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"rootDir": "./src"
},
"include": ["src"],
"exclude": ["node_modules", "tests", "examples", "dist"]
}
# 安装 ESLint 和 Prettier
npm install eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin prettier eslint-config-prettier eslint-plugin-prettier --save-dev
# 创建 ESLint 配置
创建 .eslintrc.js
:
module.exports = {
parser: "@typescript-eslint/parser",
extends: [
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
],
parserOptions: {
ecmaVersion: 2018,
sourceType: "module",
},
rules: {
// 自定义规则
},
};
创建 .prettierrc
:
{
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100,
"tabWidth": 2,
"semi": true
}
# 安装 Jest
npm install jest ts-jest @types/jest --save-dev
创建 jest.config.js
:
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
testMatch: ["**/tests/**/*.test.ts"],
collectCoverage: true,
coverageDirectory: "coverage",
collectCoverageFrom: ["src/**/*.ts"],
};
编辑 package.json
添加以下脚本:
"scripts": {
"build": "tsc",
"test": "jest",
"lint": "eslint 'src/**/*.ts'",
"format": "prettier --write 'src/**/*.ts'",
"prepare": "npm run build",
"prepublishOnly": "npm test && npm run lint",
"preversion": "npm run lint",
"version": "npm run format && git add -A src",
"postversion": "git push && git push --tags"
}
在 src/index.ts
创建 SDK 的入口点:
// 导出所有公共 API
export * from "./client";
export * from "./types";
// ... 其他导出
// 导出默认客户端
import { Client } from "./client";
export default Client;
例如,创建 src/client.ts
实现主要客户端类:
import { ApiResponse, ClientOptions } from "./types";
export class Client {
private apiKey: string;
private baseUrl: string;
constructor(options: ClientOptions) {
this.apiKey = options.apiKey;
this.baseUrl = options.baseUrl || "https://api.example.com/v1";
}
async makeRequest<T>(
endpoint: string,
method: string = "GET",
data?: any,
): Promise<ApiResponse<T>> {
const url = `${this.baseUrl}/${endpoint}`;
try {
const response = await fetch(url, {
method,
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${this.apiKey}`,
},
body: data ? JSON.stringify(data) : undefined,
});
const responseData = await response.json();
if (!response.ok) {
throw new Error(responseData.message || "API request failed");
}
return {
data: responseData,
status: response.status,
headers: response.headers,
};
} catch (error) {
throw error;
}
}
// 公共 API 方法
async getUser(userId: string) {
return this.makeRequest<any>(`users/${userId}`);
}
// 更多 API 方法...
}
创建 src/types.ts
定义类型:
export interface ClientOptions {
apiKey: string;
baseUrl?: string;
}
export interface ApiResponse<T> {
data: T;
status: number;
headers: Headers;
}
// 更多类型定义...
将 SDK 拆分为多个模块,例如:
src/
index.ts # 主入口
client.ts # 核心客户端类
types.ts # 类型定义
modules/
auth.ts # 认证相关功能
users.ts # 用户相关功能
products.ts # 产品相关功能
utils/
request.ts # 请求工具
validation.ts # 验证工具
在 tests
目录中创建测试文件,例如 tests/client.test.ts
:
import { Client } from "../src/client";
// 模拟 fetch API
global.fetch = jest.fn();
describe("Client", () => {
let client: Client;
beforeEach(() => {
client = new Client({ apiKey: "test-api-key" });
(global.fetch as jest.Mock).mockClear();
});
test("makeRequest should call fetch with correct parameters", async () => {
(global.fetch as jest.Mock).mockResolvedValueOnce({
ok: true,
status: 200,
json: async () => ({ id: "123", name: "Test User" }),
headers: new Headers(),
});
const result = await client.makeRequest("users/123");
expect(global.fetch).toHaveBeenCalledWith(
"https://api.example.com/v1/users/123",
expect.objectContaining({
method: "GET",
headers: expect.objectContaining({
"Authorization": "Bearer test-api-key",
}),
}),
);
expect(result.data).toEqual({ id: "123", name: "Test User" });
});
// 更多测试...
});
npm test
创建与真实 API 交互的集成测试,例如 tests/integration.test.ts
:
import { Client } from "../src/client";
// 这些测试需要一个有效的 API 密钥
// 通常通过环境变量提供
const apiKey = process.env.API_KEY;
// 只在提供 API 密钥时运行集成测试
(apiKey ? describe : describe.skip)("Integration tests", () => {
let client: Client;
beforeAll(() => {
client = new Client({ apiKey });
});
test("can get a user", async () => {
const result = await client.getUser("test-user-id");
expect(result.status).toBe(200);
expect(result.data).toHaveProperty("id");
});
// 更多集成测试...
});
编辑 package.json
指定要包含的文件:
{
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist",
"LICENSE",
"README.md"
]
}
如果你想同时支持 CommonJS 和 ES 模块,可以使用 rollup
或 tsup
等工具。
使用 tsup
示例:
# 安装 tsup
npm install tsup --save-dev
更新 package.json
:
{
"main": "dist/index.js",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"exports": {
".": {
"require": "./dist/index.js",
"import": "./dist/index.mjs",
"types": "./dist/index.d.ts"
}
},
"scripts": {
"build": "tsup src/index.ts --format cjs,esm --dts --clean"
}
}
确保你的包名是唯一的,并已登录 npm。如果使用组织名称,确保已创建组织。
# 检查是否登录
npm whoami
# 如果未登录
npm login
在发布前,使用 npm pack
创建一个本地包进行测试:
npm pack
这将创建一个 .tgz
文件,可以在另一个项目中使用它进行测试:
# 在测试项目中
npm install ../path/to/your-package-1.0.0.tgz
# 首次发布
npm publish
# 如果是组织包并想设为公共
npm publish --access public
修改 package.json
中的版本号或使用 npm version
命令:
# 更新补丁版本 (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 publish
一个好的 README 应包含:
- 项目描述
- 安装指南
- 基本用法示例
- API 文档链接
- 配置选项
- 常见问题
示例 README 结构:
# My Awesome SDK
A JavaScript/TypeScript SDK for interacting with the Example API.
## Installation
```bash
npm install my-awesome-sdk
```
import Client from "my-awesome-sdk";
const client = new Client({ apiKey: "your-api-key" });
// Get a user
client.getUser("user123")
.then((response) => console.log(response.data))
.catch((error) => console.error(error));
For full documentation, visit docs.example.com.
The client accepts the following options:
Option | Type | Required | Default | Description |
---|---|---|---|---|
apiKey | string | Yes | - | Your API key |
baseUrl | string | No | https://api.example.com/v1 | The API base URL |
MIT
### 2. 示例代码
在 `examples` 目录中创建示例代码:
```typescript
// examples/basic-usage.ts
import Client from '../src';
async function main() {
const client = new Client({ apiKey: 'your-api-key' });
try {
const user = await client.getUser('user123');
console.log('User:', user.data);
// More examples...
} catch (error) {
console.error('Error:', error);
}
}
main();
可以使用 TypeDoc 生成 API 文档:
npm install typedoc --save-dev
添加 package.json
脚本:
"scripts": {
"docs": "typedoc --out docs src/index.ts"
}
- 补丁版本(1.0.x):修复 bug,小改进
- 小版本(1.x.0):添加向后兼容的新功能
- 大版本(x.0.0):不向后兼容的变更
创建 CHANGELOG.md
并记录每个版本的变更:
# Changelog
## [1.1.0] - 2023-05-01
### Added
- New feature X
- Support for Y
### Changed
- Improved error handling
- Better performance for Z
### Fixed
- Bug in getUser method
## [1.0.0] - 2023-04-01
Initial release
创建 .github/workflows/ci.yml
:
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x, 16.x, 18.x]
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run build
- run: npm test
- run: npm run lint
创建 .github/workflows/publish.yml
:
name: Publish
on:
release:
types: [created]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '16.x'
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm run build
- run: npm test
- run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
在 GitHub 仓库设置中,添加 NPM_TOKEN
秘密。
- 简单直观:API 应该易于理解和使用
- 一致性:保持命名和模式一致
- 错误处理:提供清晰的错误信息和类型
- 文档完善:每个公共方法都应有文档
- 链式调用:考虑支持方法链接
创建自定义错误类:
// src/errors.ts
export class ApiError extends Error {
statusCode: number;
data: any;
constructor(message: string, statusCode: number, data?: any) {
super(message);
this.name = "ApiError";
this.statusCode = statusCode;
this.data = data;
Object.setPrototypeOf(this, ApiError.prototype);
}
}
export class ValidationError extends Error {
errors: any[];
constructor(message: string, errors: any[]) {
super(message);
this.name = "ValidationError";
this.errors = errors;
Object.setPrototypeOf(this, ValidationError.prototype);
}
}
如果 SDK 需要在浏览器中运行,考虑:
- 使用
fetch
时提供兼容性封装 - 考虑使用
axios
等库处理请求 - 使用 polyfills 确保兼容性
- 不要在客户端代码中存储敏感信息
- 使用 HTTPS
- 避免将敏感信息记录到控制台
- 提供 API 令牌轮换机制
- 包名冲突:在 npm 上查找是否已存在同名包
- 构建错误:检查 TypeScript 配置和依赖
- 发布失败:确保已登录 npm 并有权限发布
// 添加调试配置
export class Client {
private debug: boolean;
constructor(options: ClientOptions) {
this.debug = options.debug || false;
// ...
}
private log(...args: any[]) {
if (this.debug) {
console.log("[SDK]", ...args);
}
}
async makeRequest<T>(
endpoint: string,
method: string = "GET",
data?: any,
): Promise<ApiResponse<T>> {
this.log("Request:", method, endpoint, data);
// ...请求逻辑...
this.log("Response:", responseData);
return response;
}
}
- 在 GitHub 上设置 Issues 模板
- 提供常见问题和解决方案
- 考虑设置讨论区或社区渠道
您想要建立一个名为"aastar"的NPM组织,其中包含多个子包,既可以单独安装也可以整体安装。这是一个非常好的组织方式,类似于许多流行的库如Next.js、Ant Design等采用的结构。
要实现这样的结构,您需要采用"Monorepo"(单体仓库)架构,以下是详细的规划和实现步骤:
首先,建立基本的项目结构:
aastar/
├── packages/
│ ├── airaccount/ # 子包1
│ ├── superpaymaster/ # 子包2
│ └── ... # 其他子包
├── package.json # 根项目配置
├── pnpm-workspace.yaml # 工作区配置
├── lerna.json # (可选)Lerna配置
└── tsconfig.json # 基础TypeScript配置
在根目录创建pnpm-workspace.yaml
文件:
packages:
- 'packages/*'
在每个子包目录中创建package.json
,例如packages/airaccount/package.json
:
{
"name": "@aastar/airaccount",
"version": "0.1.0",
"description": "AIR账户管理模块",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc",
"test": "jest"
},
"dependencies": {
// 此包的依赖
},
"peerDependencies": {
// 可选的同级依赖
},
"publishConfig": {
"access": "public"
}
}
在packages
目录下创建一个集合包,例如packages/aastar/package.json
:
{
"name": "aastar",
"version": "0.1.0",
"description": "AASTAR SDK全集合",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc"
},
"dependencies": {
"@aastar/airaccount": "workspace:*",
"@aastar/superpaymaster": "workspace:*"
// 添加其他所有子包作为依赖
},
"publishConfig": {
"access": "public"
}
}
然后创建相应的入口文件packages/aastar/src/index.ts
,导出所有子包:
export * from "@aastar/airaccount";
export * from "@aastar/superpaymaster";
// 导出其他所有子包
您可以使用以下工具之一来管理版本和发布:
- 安装changesets:
pnpm add -D -w @changesets/cli
pnpm changeset init
- 配置发布脚本:在根目录的
package.json
中添加:
{
"scripts": {
"build": "pnpm -r build",
"version": "changeset version",
"publish": "pnpm build && changeset publish"
}
}
- 安装Lerna:
pnpm add -D -w lerna
- 创建
lerna.json
:
{
"version": "independent",
"npmClient": "pnpm",
"useWorkspaces": true,
"command": {
"publish": {
"ignoreChanges": ["*.md"],
"message": "chore(release): publish"
}
}
}
如果子包之间有依赖关系,在依赖包的package.json
中添加:
{
"dependencies": {
"@aastar/some-dependency": "workspace:*"
}
}
创建一个根目录的tsconfig.json
作为基础配置:
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"declaration": true,
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
}
}
然后,每个子包可以扩展此配置:
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"]
}
-
创建npm组织:
- 在npm网站上注册
aastar
组织
- 在npm网站上注册
-
发布所有包:
# 使用changesets
pnpm changeset
pnpm version
pnpm publish
# 或使用Lerna
pnpm lerna publish
用户可以按您希望的方式安装和使用SDK:
# 安装单个包
pnpm install @aastar/airaccount @aastar/superpaymaster
# 或安装全部功能
pnpm install aastar
使用示例:
// 单个包使用方式
import { someFunction } from "@aastar/airaccount";
// 全部功能使用方式
import { airaccount, superpaymaster } from "aastar";
这种结构的主要优点是:
- 用户可以根据需求选择安装单个包或全部功能
- 包之间可以共享代码,但又保持独立发布
- 可以统一管理版本,简化发布流程
- 便于维护和扩展
您对这种结构有什么特别的需求或问题吗?