Store Builder

CLI sb

Làm đúng những việc đó từ terminal: lệnh sb dựng sẵn một app chạy được, chạy nó với một cửa hàng thật, và đẩy một phiên bản lên mà không cần mở trình duyệt.

sb là một công cụ dòng lệnh nhỏ, lo giúp bạn phần cơ học của việc dựng một app: nó viết ra một app chạy được ngay, nó tải mã của một island lên trong lúc bạn đang sửa, và nó đẩy payload của một phiên bản theo đúng thứ tự mà nền tảng chấp nhận.

Nó là đường đi nhanh hơn qua cùng một vùng đất, không phải một vùng đất khác. Mọi việc sb làm đều là một request HTTP đã được mô tả ở chỗ khác — Xây một app cho app và luồng OAuth của nó, Xây một block cho block và island. Không có gì ở đây chỉ đến được bằng CLI, và cũng không có gì ở đây giấu đi một request mà bạn không tự gọi được. Khi một lệnh từ chối, câu bạn cần thường nằm ở một trong hai trang đó; trang này cho bạn biết công cụ đã làm được đến đâu trước khi dừng.

Có ba lệnh, và trang này là tài liệu tham chiếu cho cả ba. Nếu bạn chưa từng dựng một app nào, hãy bắt đầu ở Xây một appsb giả định bạn đã có sẵn một tổ chức và một app.


1. Cài nó

@sbuilder/cli đã có trên npm. Không phải cài gì:

npx @sbuilder/cli --help

hoặc đặt hẳn vào PATH:

npm install -g @sbuilder/cli
sb --help

Nó cần Node 20 trở lên.

CLI được viết bằng TypeScript và ship ở dạng đã biên dịch: một bản cài đặt không có dependency nào và không phải build gì.

Bộ khung mà sb init sinh ra cũng cố tình như vậy — không dependency, không build, ESM thuần — vì đó mới là đoạn mã bạn thật sự chạy.


2. sb init — dựng một app chạy được

sb init my-app
sb init my-app --port 4000

Viết ra một app chạy được vào ./my-app rồi in ra việc cần làm tiếp theo. Nó không gọi mạng một lần nào: nó là công cụ dựng khung, không phải bước đăng ký. Tạo app và tạo phiên bản vẫn là việc của bạn, và đó là §2 với §3 của Xây một app.

Nó viết ra những gì — năm tệp, không hơn:

Tệp Nó là gì
server.js Toàn bộ app: phần đổi token ở /oauth/callback và trang /embed được nhúng khung, gói trong một tệp không phụ thuộc gì
package.json npm start chạy server.js. Không dependencies và không devDependencies — lần npm install đầu tiên không nên là thứ đầu tiên có thể hỏng
.env.example Hai giá trị nền tảng đưa cho bạn (SB_CLIENT_ID, SB_CLIENT_SECRET) cộng với SB_API, SB_REDIRECT_URIPORT
README.md Đúng những bước tiếp theo mà lệnh đã in ra, để dành cho lúc terminal đã cuộn mất
.gitignore node_modules.env

Trong scaffold cố ý không có block, không có island và không có dữ liệu app. Mỗi thứ đó là một tính năng thật với hướng dẫn riêng, và không thứ nào cần thiết để lần đầu tiên nhìn thấy một app hiện ra bên trong trang quản trị của một chủ cửa hàng.

Về cái tên

Tham số được chuẩn hoá thành thứ mà npm chấp nhận làm tên package: viết thường, mọi chuỗi ký tự nằm ngoài a-z 0-9 . _ - gộp lại thành một dấu -, và cắt bỏ -_. ở đầu và cuối. sb init "My App!" viết ra ./my-app. Không đưa tên nào thì nó viết ra ./my-app.

Nó từ chối cái gì

Nó không bao giờ ghi đè, và không bao giờ ghi một nửa. Trước khi viết bất cứ thứ gì, nó đối chiếu toàn bộ danh sách tệp với thư mục đích; chỉ cần một va chạm là từ chối tất cả, và nói rõ cái gì đụng nhau:

/path/to/my-app already contains package.json, server.js — refusing to overwrite.
Pick an empty directory, or move those files aside.

Một scaffold dở dang tệ hơn là không có, vì nó để lại một thư mục trông như đã khởi tạo mà thật ra chưa. Còn một thư mục đã tồn tại thì bình thường — chạy sb init bên trong một bản git clone mới tinh là chuyện rất thường, và từ chối nó chỉ vì có .git là từ chối đúng trường hợp phổ biến nhất.

Các cờ của sb init

Cờ Mặc định Tác dụng
--port <n> 3000 Cổng được ghi sẵn vào .env.example, vào README và vào mọi URL mà lệnh in ra. Một giá trị không phải số dương sẽ bị từ chối với --port must be a number

Điều nó nói với bạn mà không tài liệu nào khác nói

Không cần tunnel. Một phiên bản nháp được phép trỏ vào http://localhost, và một bản cài thử nghiệm được phép nhúng khung nó, nên toàn bộ vòng lặp phát triển chạy gọn trên máy bạn. Tunnel chỉ dùng khi bạn muốn kiểm tra các URL công khai trước lúc gửi duyệt. Đây là điều hay lấy mất của một lập trình viên lần đầu cả tiếng đồng hồ nhất, nên lệnh nói thẳng ra thay vì để dành cho §1 của Xây một app.


3. sb.json — tệp mà devdeploy đọc

Cả sb dev lẫn sb deploy đều đọc một sb.json từ thư mục hiện tại.

sb init không viết nó, và cũng không thể viết: ba trong năm trường của nó là những id do nền tảng cấp khi bạn tạo app và tạo phiên bản nháp, những việc chưa xảy ra vào lúc init. Hãy tự viết nó khi bạn đã có chúng:

{
  "orgId": "org_yourorg",
  "appId": "app_1a2b3c4d",
  "versionId": "ver_5e6f7a8b",
  "island": "countdown",
  "entry": "islands/countdown.js"
}

Năm khoá đó là bắt buộc với cả hai lệnh — thiếu một khoá sẽ bị từ chối kèm tên khoá (sb.json is missing "versionId".), còn thiếu hẳn tệp thì bị từ chối kèm danh sách những gì nó cần. sb deploy cần chúng ngay cả khi phần việc của nó được mô tả bởi các khoá tuỳ chọn bên dưới, nên hãy điền đủ kể cả khi bạn chỉ định deploy block.

sb deploy đọc thêm ba khoá nữa, tất cả đều tuỳ chọn. Khoá nào nó tìm thấy sẽ thành một bước; khoá nào không thấy thì bỏ qua:

Khoá Kiểu sb deploy làm gì với nó
version object PUT như các trường của phiên bản — scopes, embedUrl, redirectUri và phần còn lại
blocks đường dẫn Manifest của block, PUT làm body của .../blocks. Xem Xây một block
islands mảng {name, entry} Khai báo mọi name, rồi tải lên mọi entry

Một sb.json chỉ có năm khoá bắt buộc là hợp lệ: sb dev chạy được, còn sb deploy sẽ không tìm thấy bước nào để chạy.


4. sb dev — lưu, tải lên, tải lại trang

SB_TOKEN=<token của bạn> sb dev

Theo dõi tệp entry trong sb.json và tải nó lên island island của phiên bản mỗi lần bạn lưu. Nó tải lên một lần ngay khi khởi động chứ không chờ lần lưu đầu tiên của bạn, rồi chạy cho tới khi bạn ngắt.

Nó không phải hot reload, và nó sẽ không bao giờ nói nó là hot reload. Mã của một island trên một trang đã render luôn đến từ object store của nền tảng, nên không thể trỏ một trang vào máy dev của bạn và cũng chẳng có gì để vá nóng. Thứ lệnh này bỏ đi là công đoạn phải đi tìm endpoint: bạn lưu, nó tải lên, bạn tải lại trang. Nó nói đúng như vậy trong terminal, mỗi lần:

uploaded countdown — refresh the page to see it

Nó tải lên một bản nháp. Một phiên bản đã rời trạng thái nháp sẽ từ chối ghi — việc duyệt ghim chặt một payload, và một island bị tráo sau khi duyệt là mã đang chạy trên trang của các chủ cửa hàng mà không ai từng xem qua. Xem §6 để biết lời từ chối đó trông như thế nào.

sb dev không nhận cờ nào. Nó tải lên một island — cái được nêu tên trong sb.json. Muốn đẩy nhiều island cùng lúc, dùng sb deploy với mảng islands.


5. sb deploy — trọn payload, theo thứ tự phụ thuộc

SB_TOKEN=<token của bạn> sb deploy
SB_TOKEN=<token của bạn> sb deploy --submit

Chạy mọi bước mà sb.json của bạn mô tả, theo đúng thứ tự này:

version fields        PUT  /api/orgs/{orgId}/apps/{appId}/versions/{versionId}
blocks                PUT  .../blocks
island declarations   PUT  .../islands
island "<name>"       POST .../islands/module?name=<name>     (mỗi island một lần)

Thứ tự chính là thiết kế. Phần khai báo đi trước phần mã lấp vào chúng, bởi mã tải lên cho một island mà phiên bản chưa khai báo sẽ bị từ chối (island_not_found), trong khi một khai báo chưa có mã là trạng thái mà nền tảng lường trước. Nhờ vậy một lần hỏng giữa chừng để lại đúng hình dạng của thứ bạn định làm, chứ không phải mấy mảnh byte mồ côi.

Nó không nguyên tử, và nó không cần nguyên tử

Payload của một phiên bản được ghi qua năm cửa và không lời gọi đơn lẻ nào mang hết được, nên một công cụ xếp chúng thành hàng rồi gọi kết quả là "nguyên tử" sẽ đang hứa hẹn đúng ở chỗ mà bạn sẽ dựa vào lời hứa đó. Nó không cần lời hứa ấy: một bản nháp vốn được thiết kế để có thể thiếu nhất quán giữa các lần ghi. Cả hai phép kiểm tra tính nhất quán đều chạy lúc gửi duyệt chứ không chạy trên từng lần ghi, nên một lần deploy dang dở cho ra một phiên bản chưa xong chứ không phải một phiên bản hỏng — và cái giá phải trả rất rõ ràng: nó không gửi duyệt được cho tới khi làm nốt, và lời từ chối lúc gửi duyệt nói đích danh cái gì còn thiếu.

Bù lại, thứ lệnh này nợ bạn là nói rõ nó dừng ở đâu:

Stopped at: island "countdown"

This version is in review or already approved, so its code is frozen.
Create a new draft version and point sb dev at that one.
(server: version has left draft)

Already applied: version fields, blocks, island declarations

This left the version UNFINISHED, not broken — a draft is allowed to be
partway through, and it simply cannot be submitted until the rest lands.
Fix the cause and run sb deploy again; every step is safe to repeat.

Mọi lần ghi đều idempotent, nên chạy tiếp đơn giản là chạy lại.

Các cờ của sb deploy

Cờ Tác dụng
--submit Sau khi deploy thành công thì POST .../submit — đúng cổng duyệt ở §8 của Xây một app

--submit trước hết soi khối version trong sb.json của bạn xem còn URL nào đang trỏ về máy bạn không, và từ chối trước cả khi gọi lên server:

This version still points at your own machine: embedUrl (http://localhost:3000/embed).
Review needs public https URLs — the loopback allowance covers development only.
Update the version to its real addresses, deploy again, then submit.

Phép kiểm tra đó là một sự lịch sự, không bao giờ là thẩm quyền. Server mới chạy phép kiểm tra thật, và nếu hai bên có lúc nào bất đồng thì server đúng; cái này tồn tại chỉ để hỏng sớm hơn, với nhiều chỗ để giải thích hơn một mã trạng thái.


6. Biến môi trường, và những lời từ chối

Hai biến, và chỉ hai:

Biến Lệnh nào cần Mặc định
SB_TOKEN sb dev, sb deploy không có — thiếu là cả hai dừng ngay
SB_API sb dev, sb deploy http://localhost:8080

SB_TOKEN là token của tổ chức sở hữu app — đúng cái user access token bạn sẽ đặt vào header Authorization: Bearer khi gọi các route thuộc phạm vi tổ chức. Nó không phải token wba_ của app; loại đó thuộc về một bản cài, và một bản cài không sửa được chính app mà nó là bản cài của.

Mọi lệnh thoát 0 khi thành công và 1 khi từ chối vì một lý do nó giải thích được. Bất cứ mã nào khác là lỗi của công cụ và sẽ kèm stack trace, bởi một công cụ dựng khung mà nuốt lỗi của chính nó là công cụ không ai báo lỗi được.

Những lời từ chối mà công cụ dịch lại thay vì chuyển nguyên văn:

Mã từ server sb nói gì thay vào đó
not_draft Phiên bản này đang chờ duyệt hoặc đã được duyệt nên mã của nó bị đóng băng — hãy tạo một bản nháp mới và trỏ vào đó
island_not_found Phiên bản này không khai báo island nào tên như vậy; khai báo trước đã
island_module_not_text Tệp đó không phải văn bản. Một island module là JavaScript
island_module_too_large Tệp đó quá lớn để tải lên làm một island module
unauthorized Chưa đăng nhập. Đặt SB_TOKEN là token của tổ chức sở hữu app này
bất kỳ 401/403 nào khác Token này không sửa được app đó — kiểm tra lại SB_TOKEN, và kiểm tra app có thuộc tổ chức bạn nêu tên không

Mỗi dòng trong đó đều giữ nguyên câu của server trong (server: …) rồi thêm vào hành động tiếp theo. Một mã mà công cụ chưa được dạy sẽ được in bằng đúng lời của server chứ không phải một cái nhún vai — §10 của Xây một appXây một block có bảng liệt kê chúng.


7. Những gì nó không làm

Nói ra để bạn liệu đường mà tính, thay vì tự phát hiện.

  • Nó không tạo app, không tạo phiên bản, không tạo bản cài. sb init không gọi mạng lần nào; §2, §3 và §4 của Xây một app vẫn phải làm bằng tay hoặc bằng script của bạn. CLI bắt đầu có ích khi những id đó đã tồn tại.
  • Nó không viết sb.json (§3), cùng một lý do.
  • Không có sb login. SB_TOKEN đặt trong môi trường của bạn, và công cụ không lưu credential ở đâu cả.
  • sb dev không phải hot reload (§4), và không phiên bản tương lai nào của nó có thể là hot reload chừng nào mã của island còn được phục vụ từ object store của nền tảng.
  • sb deploy không nguyên tử (§5), một cách cố ý.
  • Nó chưa có trên npm (§1).

Dựng bản thân app: Xây một app. Dựng một block hay một island cho trình sửa trang: Xây một block. Mọi thứ một token chạm tới được khi bạn đã cầm nó: API công khai, hoặc bảng điều khiển API để bắn một request vào server thật.

Cập nhật 22/8/2026