@everystack/cli

中文文档
0.4.107发布于 2026/11/01AGPL-3.0-onlyTypeScriptCommonJS

CLI and OTA updates for Expo apps on everystack

原始使用文档
文档目录25 节
  1. Install
  2. Entry Points
  3. Server: Handler
  4. Mounting
  5. Handler Endpoints
  6. Handler Options
  7. Storage Adapters
  8. Filesystem
  9. S3
  10. Custom Adapter
  11. CLI
  12. Publish an Update
  13. Code Signing
  14. Channel Management
  15. Deploy
  16. Database Management
  17. Interactive Console
  18. Client: React Native
  19. UpdatesProvider
  20. AppStateUpdateProvider
  21. Schema
  22. Expo Updates Protocol
  23. How It Works
  24. Peer Dependencies
  25. License

@everystack/cli

CLI and OTA updates for Expo apps on everystack. Database management (db:migrate, db:seed), OTA publishing, Expo Updates protocol (v0/v1), pluggable storage, RSA-SHA256 code signing, and channels.

Install

pnpm add @everystack/cli drizzle-orm structured-headers

For S3 storage:

pnpm add @aws-sdk/client-s3

Entry Points

ImportDescription
@everystack/cliServer: handler, storage adapters
@everystack/cli/clientClient: UpdatesProvider, AppStateUpdateProvider
@everystack/cli/schemaDrizzle tables (channels, releases, assets)

Server: Handler

Creates a Web Standard handler implementing the Expo Updates manifest protocol:

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
});

Mounting

// app/api/updates/[...path]+api.ts
export function GET(request: Request) { return handler(request); }
export function POST(request: Request) { return handler(request); }

Handler Endpoints

The handler serves:

  • Manifest requests — Expo Updates protocol v0/v1 manifest responses with multipart/mixed format
  • Asset downloads — Binary assets from your configured storage
  • Publish endpoint — Authenticated upload of new releases (used by the CLI)

Handler Options

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')
}

Storage Adapters

Filesystem

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
});

The S3 client is bounded by default so a single call can never hang: a connect timeout (3s), a per-request timeout (10s), bounded retries (3 attempts), and a capped list() (paged with a page backstop). This matters most for the observability rollup — an unbounded client under an S3 503 storm blocks until the Lambda's own timeout, and each hung invocation pins a concurrency slot, so it can drain the account pool and starve the app it observes.

Tune per-field via options, or override the timeouts/retries by env var without a code change (env is handy for turning a knob on a live 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)
  },
});

Precedence per field: explicit options > env var > default.

Custom Adapter

Implement the StorageAdapter interface:

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>;
}

CLI

Publish updates, manage certificates, and channels from the command line.

Publish an Update

everystack update \
  --channel production \
  --message "Fix login bug" \
  --platform ios          # ios | android | web | all (default: all)

This bundles your app, uploads assets to storage, creates a release record, and signs the manifest.

Media assets configured in app.config.js under extra.everystack.assets are fingerprinted with 8-char content hashes (e.g., logo.png → logo-a1b2c3d4.png) and synced to the media bucket. A media-manifest.json mapping logical names to fingerprinted paths is written to the bucket root. Stale fingerprinted files from previous deploys are pruned automatically.

// app.config.js
extra: {
  everystack: {
    assets: [
      { from: 'server/email/assets', to: 'email/', include: '*.{png,jpg}' },
      { from: 'assets/og', to: 'og/' },
    ],
  },
}

Use @everystack/server/media to resolve fingerprinted URLs at runtime in SSR, email templates, and workers.

Code Signing

Generate RSA key pair for manifest signing:

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

Channel Management

everystack channels list
everystack channels create --name staging
everystack channels create --name production

Deploy

everystack deploy --stage dev           # provision infrastructure (wraps `sst deploy`)

A thin wrapper over SST so you never type sst directly. Infrastructure only — migrations and bundle publishing stay explicit (db:migrate, update).

Database Management

These commands invoke your Lambda directly via IAM (no database credentials ever leave 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

The full command reference — including secrets, logs:*, status, cache:purge, branches, and every flag — is in docs/cli.md.

Interactive Console

everystack console --stage dev          # Connect to deployed database
everystack console --stage dev --sandbox  # Sandbox mode: rolls back after each expression

A REPL connected to your deployed database with db, schema, and eq/and/or helpers in scope. Supports multiline input, persistent history, and tab completion.

Dot commands:

  • .tables / .schema [table] — Inspect database schema
  • .login — Authenticate with email/password (hidden input)
  • .user — Show current auth context
  • .logout — Clear auth context
  • .sandbox — Toggle sandbox mode

After .login, queries run with RLS policies applied via pgSettings. The current_user variable is available in scope. Passwords are redacted from history.

Client: React Native

UpdatesProvider

Wraps your app to check for and apply OTA updates:

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

Checks for updates when the app returns from background:

import { AppStateUpdateProvider } from '@everystack/cli/client';

function App() {
  return (
    <AppStateUpdateProvider url="https://myapp.com/api/updates" channel="production">
      <MyApp />
    </AppStateUpdateProvider>
  );
}

Schema

Add the updates tables to your Drizzle migrations:

import { channels, releases, assets } from '@everystack/cli/schema';

Tables:

  • channels — Named release channels (production, staging, etc.)
  • releases — Published update bundles with metadata
  • assets — Individual asset files referenced by releases

Expo Updates Protocol

The handler implements the full Expo Updates manifest protocol:

  • Protocol v0: Legacy format for older Expo SDK versions
  • Protocol v1: Modern multipart/mixed response format
  • Code signing: RSA-SHA256 signatures on manifest directives
  • Platform filtering: Serves platform-specific bundles based on request headers
  • Channel routing: Multiple release channels with independent version tracks

How It Works

  1. The Expo app sends a manifest request with platform, runtime version, and current update ID
  2. The handler finds the latest release for the requested channel and platform
  3. If a newer release exists, it returns a signed manifest with asset URLs
  4. The Expo runtime downloads assets and applies the update

Peer Dependencies

PackageVersionRequired
drizzle-orm>=0.30.0Yes
structured-headers^1.0.0Yes (runtime dep)
@aws-sdk/client-s3>=3.0.0For S3 storage
expo-updates>=0.25.0Client SDK
react>=18.0.0Client SDK
react-native>=18.0.0Client SDK

License

AGPL-3.0-only © Scalable Technology, Inc.

A commercial license is available for organizations that cannot or do not wish to comply with the AGPL-3.0 terms. For commercial licensing, contact [email protected].

版本与发布记录

@everystack/cli 共发布 160 个版本,首次发布于 2026/05/15;过去 12 个月发布了 160 个版本。

版本发布日期距今
0.4.107latest2026/11/012 天前
0.4.1062026/11/012 天前
0.4.1052026/11/012 天前
0.4.1042026/10/313 天前
0.4.1032026/10/259 天前
0.4.1022026/10/2410 天前

其他发布通道:v0-4 → 0.4.64。

关于 @everystack/cli 的常见问题

@everystack/cli 是什么?

博览会应用的CLI和OTA更新

如何安装 @everystack/cli?

在项目目录运行 npm install @everystack/cli,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:drizzle-orm(0.41.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。它还提供命令行工具 everystack,可以用 npx everystack 直接运行。

@everystack/cli 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@everystack/cli 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@everystack/cli 可以商用吗?

@everystack/cli 使用 AGPL-3.0-only 许可。最严格的 Copyleft:除分发外,通过网络向用户提供基于它的服务也需要开源对应源码。闭源 SaaS 使用前务必评估。以上为概括,不构成法律意见,请以许可原文为准。

@everystack/cli 还在维护吗?

最新版本 0.4.107 发布于 2026/11/01(2 天前);至今共发布 160 个版本,其中过去 12 个月发布了 160 个;首次发布于 2026/05/15。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。