Store Builder

CLI sb

Làm những việc đó từ terminal: lệnh sb dựng sẵn một app chạy được, tải mã island lên mỗi lần bạn lưu, 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 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, Xây một block cho block và island. Không có gì chỉ đến được bằng CLI, và không có gì ở đây giấu đi một request 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. Nếu bạn chưa dựng app nào, bắt đầu ở Xây một app — sb giả định bạn đã có một tổ chức và một app.


1. Cài nó

@sbuilder/cli có trên npm. Không cần cài gì:

npx @sbuilder/cli --help

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

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

Cần Node 20 trở lên. CLI ship ở dạng đã biên dịch — bản cài không có dependency và không phải build gì. Bộ khung sb init sinh ra cũng vậy: không dependency, không build, ESM thuần, vì đó 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 một app chạy được vào ./my-app rồi in ra việc cần làm tiếp. Nó không gọi mạng lần nào: nó dựng khung, không đăng ký. Tạo app và tạo phiên bản vẫn là việc của bạn — §2 và §3 của Xây một app.

Nó viết ra 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, trong một tệp không phụ thuộc gì
package.json npm start chạy server.js. Không dependencies, 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 SB_API, SB_REDIRECT_URI và PORT
README.md Đúng những bước tiếp theo lệnh đã in, cho lúc terminal đã cuộn mất
.gitignore node_modules và .env

Trong scaffold cố ý không có block, island hay 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 cho lần đầu thấy app hiện ra trong trang quản trị của một chủ cửa hàng.

Cái tên được chuẩn hoá thành thứ npm chấp nhận làm tên package: viết thường, mọi chuỗi ký tự ngoài a-z 0-9 . _ - gộp thành một dấu -, cắt -_. ở hai đầu. sb init "My App!" viết ra ./my-app; không đưa tên thì cũng là ./my-app.

Nó không bao giờ ghi đè, và không bao giờ ghi một nửa. Trước khi viết gì, nó đối chiếu cả danh sách tệp với thư mục đích; 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 thư mục đã tồn tại thì bình thường — chạy sb init trong một bản git clone mới là chuyện thường, và từ chối vì có .git là từ chối đúng trường hợp phổ biến nhất.

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

Không cần tunnel. Một phiên bản nháp được trỏ vào http://localhost, và một bản cài sandbox được nhúng khung nó, nên toàn bộ vòng lặp phát triển chạy trên máy bạn. Tunnel chỉ để thử 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 người mới cả tiếng đồng hồ, nên lệnh nói thẳng ra.


3. sb.json — tệp mà dev và deploy đọc

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

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

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

Năm khoá đó bắt buộc với cả hai lệnh — thiếu một khoá bị từ chối kèm tên (sb.json is missing "versionId".), thiếu hẳn tệp bị từ chối kèm danh sách nó cần. sb deploy cần chúng kể cả khi phần việc của nó nằm ở các khoá tuỳ chọn bên dưới.

sb deploy đọc thêm ba khoá, đều tuỳ chọn. Khoá nào có thì thành một bước:

Khoá Kiểu sb deploy làm gì
version object PUT làm các trường của phiên bản — scopes, embedUrl, redirectUri…
blocks đường dẫn Manifest 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 vẫn hợp lệ: sb dev chạy, còn sb deploy không tìm thấy bước nào.


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

SB_TOKEN=<token của bạn> SB_API=https://api.sbuilder.io.vn 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, rồi chạy tới khi bạn ngắt.

Nó không phải hot reload. Mã của một island trên 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. Thứ lệnh này bỏ đi là công đoạn đ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 mỗi lần:

uploaded countdown — refresh the page to see it

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

sb dev không nhận cờ nào và tải lên đúng một island. Nhiều island cùng lúc thì 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_API=https://api.sbuilder.io.vn sb deploy
SB_TOKEN=<token của bạn> SB_API=https://api.sbuilder.io.vn sb deploy --submit

Chạy mọi bước mà sb.json 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ế. Khai báo đi trước mã lấp vào chúng, vì mã tải lên cho một island phiên bản chưa khai báo bị từ chối (island_not_found), còn một khai báo chưa có mã là trạng thái nền tảng lường trước. Hỏng giữa chừng để lại đúng hình dạng thứ bạn định làm, không phải byte mồ côi.

Nó không nguyên tử, và không cần nguyên tử. Payload của một phiên bản đi qua nhiều cửa và không lời gọi nào mang hết được. Một bản nháp vốn được thiết kế để thiếu nhất quán giữa các lần ghi: các phép kiểm tra nhất quán chạy lúc gửi duyệt chứ không trên từng lần ghi, nên deploy dở dang cho ra phiên bản chưa xong chứ không hỏ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ì thiếu.

Bù lại, lệnh 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 là chạy lại.

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

--submit trước hết soi khối version trong sb.json xem còn URL nào trỏ về máy bạn không, và từ chối trước khi gọi 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 phải thẩm quyền. Server chạy phép kiểm tra thật, và nếu hai bên bất đồng thì server đúng; cái này 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, 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à dừng ngay
SB_API sb dev, sb deploy http://localhost:8080

Mặc định của SB_API là địa chỉ của một nền tảng chạy trên máy bạn. Với cửa hàng thật, đặt nó là https://api.sbuilder.io.vn.

SB_TOKEN là token của tổ chức sở hữu app — đúng user access token bạn đặt vào Authorization: Bearer khi gọi các route thuộc tổ chức. 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 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. Mã khác là lỗi của công cụ và kèm stack trace.

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

Mã từ server sb nói gì
not_draft Phiên bản đang chờ duyệt hoặc đã duyệt nên mã bị đóng băng — tạo một bản nháp mới và trỏ vào đó
island_not_found Phiên bản 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 island module
unauthorized Chưa đăng nhập. Đặt SB_TOKEN là token của tổ chức sở hữu app
401/403 khác Token này không sửa được app đó — kiểm lại SB_TOKEN, và app có thuộc tổ chức bạn nêu không

Mỗi dòng giữ nguyên câu của server trong (server: …) rồi thêm hành động kế tiếp. Mã công cụ chưa được dạy thì in bằng lời của server — §10 của Xây một app và §9 của Xây một block liệt kê chúng.


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

  • Không tạo app, phiên bản hay bản cài. sb init không gọi mạng; §2, §3 và §4 của Xây một app vẫn làm tay hoặc bằng script của bạn.
  • Không viết sb.json (§3), cùng lý do.
  • Không có sb login. SB_TOKEN đặt trong môi trường; công cụ không lưu credential ở đâu.
  • sb dev không phải hot reload (§4), và không thể là hot reload chừng nào mã 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ố ý.

Dựng chính app: Xây một app. Dựng block hay island cho trình sửa trang: Xây một block. Mọi thứ một token chạm tới được: 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 26/9/2026