@everystack / cli
博览会应用的CLI和OTA更新。数据库管理(db:migrate, db:seed), OTA发布,Expo Updates协议(v0/v1),可插拔存储,RSA-SHA256代码签名和通道。
安装
pnpm add @everystack/cli drizzle-orm structured-headers
对于S3存储:
pnpm add @aws-sdk/client-s3
入口点
| 进口 | 描述 |
|---|---|
@everystack/cli | 服务器:处理程序、存储适配器 |
@everystack/cli/client | 客户端:UpdatesProvider, AppStateUpdateProvider |
@everystack/cli/schema | 细雨表(渠道、发布、资产) |
服务器:处理程序
创建一个实现Expo Updates清单协议的Web标准处理程序:
import { createUpdatesHandler, createStorage } from '@everystack/cli';
import { db } from './db';
const storage = createStorage({
type: 'filesystem',
directory: './updates',
});
const handler = createUpdatesHandler({
db,
storage,
baseUrl: 'https://myapp.com',
basePath: '/api/updates',
defaultChannel: 'production',
auth: {
verifyToken: async (token) => verifyJWT(token),
},
privateKey: process.env.CODE_SIGNING_PRIVATE_KEY, // PEM for RSA-SHA256
});
越来越多
// app/api/updates/[...path]+api.ts
export function GET(request: Request) { return handler(request); }
export function POST(request: Request) { return handler(request); }
处理程序端点
处理程序提供:
- 清单要求 - Expo以多部分/混合格式更新协议v0/v1清单响应
- 资产下载 -二进制资产从您配置的存储
- 发布端点 —经过认证的新版本上传(CLI使用)
处理程序选项
interface UpdatesHandlerOptions {
db: DrizzleDb; // Drizzle instance
storage: StorageAdapter; // Filesystem or S3
baseUrl: string; // Public URL (for asset references in manifests)
basePath?: string; // URL prefix to strip
auth?: {
verifyToken: (token: string) => Promise<Record<string, unknown> | null>;
};
privateKey?: string; // PEM for RSA-SHA256 code signing
defaultChannel?: string; // Default channel (default: 'production')
}
存储适配器
文件系统
import { createStorage } from '@everystack/cli';
const storage = createStorage({
type: 'filesystem',
directory: './updates',
});
S3
const storage = createStorage({
type: 's3',
bucket: 'my-updates-bucket',
region: 'us-east-1',
endpoint: 'https://s3.us-east-1.amazonaws.com', // Optional
});
S3客户端为 默认有界 所以单个调用永远不会挂起:a
连接超时(3秒),每个请求超时(10秒),有界重试(3次尝试),
还有一个帽子 list() (有页面挡板的页面)。这是最重要的
可观察性卷起- S3 503风暴下的无界客户端阻塞直到
Lambda自己的超时,并且每个挂起的调用都固定一个并发插槽,因此
它可以耗尽账户池并使它所观察到的应用程序饿死。
每个字段通过调优 options,或通过env var覆盖超时/重试
无需更改代码(env可以方便地在Lambda上转动旋钮):
const storage = createStorage({
type: 's3',
bucket: 'my-updates-bucket',
region: 'us-east-1',
options: {
connectionTimeoutMs: 3000, // env: EVERYSTACK_S3_CONNECT_TIMEOUT_MS
requestTimeoutMs: 10000, // env: EVERYSTACK_S3_REQUEST_TIMEOUT_MS
maxAttempts: 3, // env: EVERYSTACK_S3_MAX_ATTEMPTS
listMaxKeys: 1000, // keys per ListObjectsV2 page
listMaxPages: 1000, // hard backstop before list() truncates (warns)
},
});
每个字段的优先级:显式 options > env var > default。
自定义适配器
实现 StorageAdapter 接口:
interface StorageAdapter {
put(key: string, data: Buffer | Uint8Array, contentType?: string): Promise<void>;
get(key: string): Promise<{ data: Buffer; contentType: string } | null>;
exists(key: string): Promise<boolean>;
list(prefix: string): Promise<string[]>;
delete(key: string): Promise<void>;
}
命令行界面
从命令行发布更新、管理证书和通道。
发布更新
everystack update \
--channel production \
--message "Fix login bug" \
--platform ios # ios | android | web | all (default: all)
这将捆绑您的应用程序,将资产上传到存储,创建发布记录,并签署清单。
中配置的媒体资产 app.config.js 下 extra.everystack.assets 使用8个字符的内容哈希(例如, logo.png → logo-a1b2c3d4.png),并同步到媒体桶。一个 media-manifest.json 将逻辑名映射到指纹路径写入桶根。以前部署的过时指纹文件将被自动修剪。
// app.config.js
extra: {
everystack: {
assets: [
{ from: 'server/email/assets', to: 'email/', include: '*.{png,jpg}' },
{ from: 'assets/og', to: 'og/' },
],
},
}
使用 @everystack/server/media 在SSR、电子邮件模板和工作程序的运行时解析指纹url。
代码签名
生成用于舱单签名的RSA密钥对:
everystack certs:generate --output ./certs
# Creates ./certs/private-key.pem and ./certs/certificate.pem
everystack certs:configure --input ./certs --keyid main
# Configures your app to use the generated certificates
渠道管理
everystack channels list
everystack channels create --name staging
everystack channels create --name production
部署
everystack deploy --stage dev # provision infrastructure (wraps `sst deploy`)
在SST上有一个薄包装,所以你永远不会打字 sst 直接。仅限基础设施—迁移和包发布保持显式(db:migrate, update)。
数据库管理
这些命令通过IAM直接调用Lambda(没有数据库凭据离开AWS):
# Migrations
everystack db:migrate # apply Drizzle migrations
everystack db:seed # seed (dev only)
everystack db:psql --stage dev [-c SQL] # interactive psql / one-off query via Lambda
# Model-driven schema (@everystack/model)
everystack db:generate # diff Models vs the live DB → next migration
everystack db:pull # introspect a live DB → field() Models (brownfield on-ramp)
everystack db:reconcile # deploy the derived layer (functions/views/matviews/triggers) from the declared descriptors in db/models — no migrations
everystack db:fingerprint # content-address the live base schema vs the Models — MATCH/MISMATCH
everystack db:sync # make the database match your checkout — state + compute, one verb (dev DBs)
everystack db:diff # the state edge between two declared states — no DB, CI-pure; reverse = computed rollback
everystack db:plan | db:apply # mint a reviewable plan against a target, apply it fingerprint-verified at both ends + the fast-forward rule (checkout must descend from the commit declaring the target's state); destructive plans: --confirm + snapshot + approver set when declared
everystack db:check # the CI gate per PR: merged declared state composes + artifacts match regeneration (+ ephemeral from-scratch build w/ a scratch PG)
everystack db:approvers # declare who can DESTROY: the stage's destructive-approver set (SSM, STS-verified at apply)
everystack db:backfill # one-shot data jobs in db/backfills/*.sql — content-hash identity, own per-DB record, never a schema side effect
everystack db:template:refresh # (re)build the dev template from the declared state + your db:seed script — never a data copy
everystack db:branch # per-branch dev DB minted from the template, keyed on the git branch (--list / --prune --confirm)
everystack db:fork # fork a deployed stage's DB into a feature stage (backup → presigned hand-off → restore); production is never a target
# Security
everystack db:doctor # is the DB least-privilege + RLS-subject?
everystack db:provision # role chain + converge declared ownership (ENACT grants, ADOPT plain objects)
everystack db:provision --confirm # + ADOPT SECURITY DEFINER functions / views / matviews (runtime change)
everystack db:authz:pull|diff|test|owner|report # the authorization contract + red-team
# Backups (see docs/backups.md)
everystack db:backup | db:backups | db:restore --from <id> --confirm # logical pg_dump → S3
everystack db:backup:download <id> # presigned URL to a dump (1h)
everystack db:snapshot | db:snapshots # physical RDS snapshots
完整的命令参考-包括 secrets, logs:*, status, cache:purge, branches每一面旗帜——都在 文档/ cli.md.
交互式控制台
everystack console --stage dev # Connect to deployed database
everystack console --stage dev --sandbox # Sandbox mode: rolls back after each expression
连接到已部署数据库的REPL db, schema, eq/and/or 范围内的helper。支持多行输入、持久历史记录和制表符完成。
点命令:
.tables/.schema [table]-检查数据库模式.login-使用电子邮件/密码(隐藏输入)进行身份验证.user-显示当前授权上下文.logout—清除授权上下文.sandbox-切换沙盒模式
后 .login,查询通过应用RLS策略运行 pgSettings。这个 current_user 变量在作用域中可用。密码是根据历史编辑的。
客户端:React Native
UpdatesProvider
包装你的应用来检查和应用OTA更新:
import { UpdatesProvider } from '@everystack/cli/client';
function App() {
return (
<UpdatesProvider
url="https://myapp.com/api/updates"
channel="production"
checkInterval={60000} // Check every 60 seconds
onUpdateAvailable={(update) => {
// Optional: prompt user or auto-apply
console.log('Update available:', update.message);
}}
onUpdateApplied={() => {
console.log('Update applied, restarting...');
}}
>
<MyApp />
</UpdatesProvider>
);
}
AppStateUpdateProvider
当应用程序从后台返回时检查更新:
import { AppStateUpdateProvider } from '@everystack/cli/client';
function App() {
return (
<AppStateUpdateProvider url="https://myapp.com/api/updates" channel="production">
<MyApp />
</AppStateUpdateProvider>
);
}
模式
将更新表添加到Drizzle迁移中:
import { channels, releases, assets } from '@everystack/cli/schema';
表:
- 渠道 -命名的发布渠道(生产、发布等)
- 释放 -发布带有元数据的更新包
- 资产 -发布引用的单个资产文件
博览会更新协议
处理程序实现了完整的Expo Updates清单协议:
- 协议v0:旧的Expo SDK版本的遗留格式
- 协议v1:现代多部分/混合响应格式
- 代码签名: manifest指令上的RSA-SHA256签名
- 平台过滤:根据请求头提供特定于平台的包
- 通道路由:多个发行渠道与独立的版本轨道
它是如何工作的
- Expo应用程序发送带有平台、运行时版本和当前更新ID的清单请求
- 处理程序查找所请求通道和平台的最新版本
- 如果存在更新的版本,它将返回带有资产url的签名清单
- Expo运行时下载资产并应用更新
同行依赖性
| 包 | 版本 | 要求 |
|---|---|---|
drizzle-orm | >=0.30.0 | 是 |
structured-headers | ^1.0.0 | 是(运行时深度) |
@aws-sdk/client-s3 | >=3.0.0 | 对于S3存储 |
expo-updates | >=0.25.0 | 客户端SDK |
react | >=18.0.0 | 客户端SDK |
react-native | >=18.0.0 | 客户端SDK |
许可证
agpl - 3.0 ©Scalable Technology, Inc.
对于不能或不希望使用商业许可证的组织,可以使用商业许可证 遵守AGPL-3.0条款。如需商业许可,请联系 [email protected].