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
sblà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 app — sb
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_URI và PORT |
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 và .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à dev và deploy đọ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 app và
Xâ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 initkhô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 devkhô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 deploykhô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