@sbuilder/cli
v0.1.1
Published
Scaffold, run and deploy an app for the Store Builder platform.
Maintainers
Readme
@sbuilder/cli
sb — tạo khung, chạy và triển khai một app cho nền tảng Store Builder.
🇬🇧 English version: README.md
sb init [tên] [--port 3000] tạo khung một app chạy được, đăng ký được, và mở
ngay trong trang quản trị của merchant
sb dev tải code của một island lên bản DRAFT mỗi lần bạn
lưu file. Đọc sb.json; cần SB_TOKEN.
sb deploy [--submit] đẩy toàn bộ payload của version theo đúng thứ tự
phụ thuộc. --submit thì gửi luôn đi duyệt.Cần Node 20 trở lên. Viết bằng TypeScript, publish ở dạng đã biên dịch — bản cài về không có dependency nào và không phải build gì cả.
Mục lục
- Nó là gì, và không phải là gì
- Cài đặt
- Đường đi năm phút
sb initsb.jsonsb devsb deploy- Biến môi trường
- Các từ chối, và ý nghĩa của chúng
- Những gì nó cố tình KHÔNG làm
- Xử lý sự cố
- Phát triển chính CLI này
- Giấy phép
Nó là gì, và không phải là gì
sb là một lối đ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 thứ nó làm đều là một HTTP request mà bạn hoàn toàn có thể tự gửi
bằng curl. Không có gì trên nền tảng này chỉ tiếp cận được qua CLI, và CLI
không giấu bạn request nào cả.
Cái nó bỏ bớt cho bạn là phần cơ học: viết bộ khung app đã biết nói OAuth, đi tìm endpoint upload island sau mỗi lần lưu file, và đẩy payload của một version qua năm cánh cửa riêng biệt theo đúng một thứ tự mà nền tảng chấp nhận.
Tài liệu tham chiếu đầy đủ được chính bản triển khai Store Builder của bạn phục
vụ tại /cli-docs, cùng với /app-docs (xây dựng app) và
/block-docs (blocks và islands). Trang đó là tài liệu chạy được — các
lệnh trên trang được trích ra từ chính bytes của nó và chạy thật với binary trong
CI, nên nó không thể mô tả một flag mà CLI từ chối. File README này bị ràng buộc
bởi cùng một tiêu chuẩn, qua tests/readme.test.ts.
Cài đặt
@sbuilder/cli chưa được đưa lên npm. Workflow publish đã hoàn chỉnh
(.github/workflows/publish-cli.yml) và đang chờ một npm credential, nên npx
sẽ không tìm thấy nó. Hãy chạy từ một bản checkout:
git clone https://github.com/vuluu2k/web_builder.git
cd web_builder
npm ci
npm run build --workspace cli
node cli/dist/bin/sb.js --helpĐể có lệnh sb nằm sẵn trong PATH thay vì phải gõ cả đường dẫn mỗi lần:
npm run build --workspace cli
cd cli && npm link
sb --helpTừ đây trở xuống đều viết là sb; node cli/dist/bin/sb.js luôn là cùng một
thứ. Nếu sb báo một lệnh hoặc một flag mà trang này không mô tả, hãy build lại
— bạn đang chạy một dist/ cũ hơn phần source nằm cạnh nó.
Bước build là dependency duy nhất của CLI. Bộ khung mà sb init sinh ra thì
cố tình ngược lại và sẽ giữ như vậy: không dependency, không build, ESM thuần —
vì đó mới là code bạn thật sự chạy.
Đường đi năm phút
Toàn bộ vòng lặp, từ đầu đến cuối. Bước 2 và 3 là phần của bạn — xem /app-docs
— còn lại mỗi bước là một câu lệnh.
# 1. Một app chạy được trên máy bạn
sb init my-app --port 4000
cd my-app
cp .env.example .env # điền SB_CLIENT_ID và SB_CLIENT_SECRET
npm start- Tạo app trong tổ chức của bạn, ghi lại
clientId/clientSecretmà nền tảng trả về. Secret chỉ hiện một lần duy nhất. - Tạo một version DRAFT trỏ vào URL local — embed URL
http://localhost:4000/embed, redirect URIhttp://localhost:4000/oauth/callback. Không cần tunnel: một version draft được phép trỏ vàohttp://localhostvà một install sandbox được phép nhúng nó, nên toàn bộ vòng lặp phát triển chạy ngay trên máy bạn. Tunnel chỉ cần khi bạn muốn kiểm tra các URL public trước lúc gửi duyệt.
# 4. Viết sb.json bằng các id vừa có (xem bên dưới), rồi:
SB_TOKEN=<token của bạn> sb dev # sửa island, lưu, refresh
# 5. Đẩy toàn bộ payload, và gửi đi duyệt
SB_TOKEN=<token của bạn> sb deploy --submitsb init — tạo khung một app chạy được
sb init my-app
sb init my-app --port 4000Ghi một app chạy được vào ./my-app và in ra việc cần làm tiếp theo. Nó không
gọi mạng một lần nào — đây là công cụ tạo khung, không phải bước đăng ký. Việc
tạo app và tạo version vẫn là của bạn.
Nó ghi ra những gì
Năm file, và không gì khác:
| File | Là gì |
| --- | --- |
| server.js | Toàn bộ app: phần đổi token ở /oauth/callback và trang /embed được nhúng, gói trong một file, không dependency |
| 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 hỏng |
| .env.example | SB_CLIENT_ID, SB_CLIENT_SECRET, SB_API, SB_REDIRECT_URI và PORT |
| README.md | Đúng những bước tiếp theo mà lệnh đã in, phòng khi terminal đã cuộn mất |
| .gitignore | node_modules và .env |
Cố tình không có blocks, không có islands, không có app data. 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 nhìn thấy app hiện ra trong trang quản trị của một merchant.
Về cái tên
Tham số đượ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 . _ - bị gộp thành một dấu -, và cắt bỏ -_. ở đầu
và cuối.
| Bạn gõ | Bạn nhận được |
| --- | --- |
| sb init my-app | ./my-app |
| sb init "My App!" | ./my-app |
| sb init | ./my-app |
Nó từ chối những gì
Không bao giờ ghi đè, và không bao giờ ghi nửa vời. Toàn bộ tập file được đối chiếu với thư mục đích trước khi ghi bất cứ thứ gì; chỉ cần một file trùng là từ chối tất cả và nêu tên thứ bị trùng:
/path/to/my-app already contains package.json, server.js — refusing to overwrite.
Pick an empty directory, or move those files aside.Một bộ khung dở dang còn 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ực ra chưa. Còn một thư mục đã tồn tại sẵn thì hoàn toàn ổn
— chạy sb init trong một bản git clone mới tinh là chuyện bình thường, từ
chối chỉ vì có .git là từ chối đúng trường hợp phổ biến nhất.
Flags
| Flag | Mặc định | Tác dụng |
| --- | --- | --- |
| --port <n> | 3000 | Cổng được ghi vào .env.example, vào README sinh ra, và vào mọi URL mà lệnh in ra. Giá trị không phải số dương sẽ bị từ chối với --port must be a number |
sb.json — file mà dev và deploy đọc
Cả sb dev lẫn sb deploy đều đọc sb.json từ thư mục hiện tại.
sb init không ghi file này, và không thể ghi được: ba trong năm trường bắt
buộc là các id do nền tảng sinh ra khi bạn tạo app và bản draft của nó — những
việc chưa xảy ra ở thời điểm init. Hãy tự viết một lần khi đã có chúng:
{
"orgId": "org_yourorg",
"appId": "app_1a2b3c4d",
"versionId": "ver_5e6f7a8b",
"island": "countdown",
"entry": "islands/countdown.js"
}Bắt buộc — thiếu một trong năm là cả hai lệnh đều từ chối
| Khoá | Là gì |
| --- | --- |
| orgId | Tổ chức sở hữu app |
| appId | App |
| versionId | Bản draft đang được sửa |
| island | Island mà sb dev tải code lên |
| entry | File mà sb dev theo dõi, tính tương đối với sb.json |
Thiếu khoá nào sẽ bị gọi tên khoá đó — sb.json is missing "versionId". — và
thiếu hẳn file thì bị từ chối kèm danh sách những gì cần có. sb deploy cũng đòi
đủ năm khoá dù công 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 đủ ngay cả khi bạn chỉ deploy blocks.
Tuỳ chọn — mỗi khoá tìm thấy trở thành một bước deploy
| Khoá | Kiểu | sb deploy làm gì với nó |
| --- | --- | --- |
| version | object | PUT làm các trường của version — 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 |
| 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, còn
sb deploy sẽ không tìm thấy bước nào để chạy.
Một file đầy đủ hơn:
{
"orgId": "org_yourorg",
"appId": "app_1a2b3c4d",
"versionId": "ver_5e6f7a8b",
"island": "countdown",
"entry": "islands/countdown.js",
"version": {
"scopes": ["products.read", "orders.read"],
"embedUrl": "https://app.example.com/embed",
"redirectUri": "https://app.example.com/oauth/callback"
},
"blocks": "blocks.json",
"islands": [
{ "name": "countdown", "entry": "islands/countdown.js" },
{ "name": "stock-bar", "entry": "islands/stock-bar.js" }
]
}sb dev — lưu, tải lên, refresh
SB_TOKEN=<token của bạn> sb devTheo dõi file entry khai trong sb.json và tải nó lên island của version mỗi
lần bạn lưu. Nó tải lên ngay một lần lúc khởi động chứ không chờ lần lưu đầu
tiên, rồi chạy cho tới khi bạn ngắt.
watching islands/countdown.js — save it and this uploads to version ver_5e6f7a8b.
This is not hot reload: after each upload, refresh the page.
uploaded countdown — refresh the page to see itĐây không phải hot reload, và nó sẽ không bao giờ tự nhận như vậy. Code 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 dev server ở máy bạn, và cũng không có chuyện vá code trực tiếp. Cái mà 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 refresh trang.
Nó tải lên một bản draft. Một version đã rời trạng thái draft sẽ từ chối ghi — việc duyệt ghim chặt payload, và một island bị tráo sau khi duyệt là code chạy trên trang của merchant mà không ai nhìn qua.
sb dev không nhận flag nào. Nó tải lên một island — cái được đặt tên trong
sb.json. Muốn đẩy nhiều island một lúc thì dùng sb deploy với mảng islands.
sb deploy — toàn bộ 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 --submitChạ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ế. Phần khai báo đi trước các module lấp vào chúng, vì
code tải lên cho một island mà version chưa khai báo sẽ bị từ chối
(island_not_found), trong khi một khai báo chưa có code là trạng thái nền tảng
hoàn toàn chấp nhận. Nhờ vậy một lần dừng giữa chừng để lại đúng hình dạng thứ
bạn định làm, chứ không phải mấy đống bytes mồ côi.
Nó không nguyên tử, và nó không cần phải thế
Payload của một version được ghi qua năm cánh cửa, không lời gọi đơn lẻ nào chở hết được; một công cụ xâu chuỗi chúng lại rồi tuyên bố kết quả là nguyên tử thì đang hứa hẹn đúng ở chỗ mà bạn sẽ tin vào lời hứa đó. Mà nó không cần lời hứa ấy: một bản draft được thiết kế để có thể không nhất quán giữa các lần ghi. Cả hai bước kiểm tra tính nhất quán đều chạy lúc submit chứ không phải ở từng lệnh ghi, nên một lần deploy dở dang cho ra một version chưa xong chứ không phải một version hỏng.
Đổi 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ệnh ghi đều idempotent, nên "chạy tiếp" đơn giản là chạy lại.
Flags
| Flag | Tác dụng |
| --- | --- |
| --submit | Sau khi deploy thành công thì POST .../submit — cổng duyệt |
--submit trước tiên soi khối version trong sb.json của bạn 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.Kiểm tra đó là phép lịch sự, không bao giờ là thẩm quyền. Server mới chạy bản kiểm tra thật, và nếu hai bên có mâu thuẫn thì server thắng; cái này chỉ tồn tại để hỏng sớm hơn, với nhiều chỗ giải thích hơn một status code.
Biến môi trường
Hai biến do chính CLI đọc:
| Biến | Lệnh cần 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 theo tổ chức. Nó
không phải app token dạng wba_; loại đó thuộc về một install, và một install
không thể sửa chính cái app mà nó là install của.
Ba biến còn lại do app mà sb init sinh ra tự đọc, từ .env của chính nó:
| Biến | Ai đọc | Là gì |
| --- | --- | --- |
| SB_CLIENT_ID | server.js được sinh ra | Hiện lên khi bạn tạo app |
| SB_CLIENT_SECRET | server.js được sinh ra | Chỉ hiện một lần lúc tạo. Mất thì xoay vòng lại, đừng đi tìm |
| SB_REDIRECT_URI | server.js được sinh ra | Phải khớp từng ký tự với redirect URI trên version của bạn |
App sinh ra cũng đọc SB_API và PORT.
Các từ chối, và ý nghĩa của chúng
Mọi lệnh thoát 0 khi thành công và 1 khi từ chối có lý do giải thích được.
Ngoài hai giá trị đó là bug của chính công cụ, và khi đó nó in stack trace, vì
một công cụ tạo khung mà nuốt lỗi của chính mình là công cụ không ai báo lỗi
được.
Các mã lỗi từ server mà CLI dịch lại thay vì bê nguyên văn:
| Mã từ server | sb nói gì thay vào đó |
| --- | --- |
| not_draft | Version này đang duyệt hoặc đã được duyệt nên code bị đóng băng — tạo bản draft mới và trỏ vào đó |
| island_not_found | Version này không khai báo island tên đó; khai báo trước đã |
| island_module_not_text | File đó không phải text. Một island module là JavaScript |
| island_module_too_large | File đó 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 này |
| mọi 401/403 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 khai không |
Mỗi dòng đều giữ nguyên câu chữ của server trong (server: …) rồi thêm việc cần
làm tiếp. Một mã lỗi công cụ chưa được dạy sẽ được in bằng chính lời của server,
chứ không phải một cái nhún vai.
Những gì nó cố tình KHÔNG làm
Nói ra để bạn tính đường trước, thay vì tự phát hiện giữa chừng.
- Nó không tạo app, không tạo version, không tạo install.
sb initkhông gọi mạng lần nào. Những bước đó là của bạn, làm tay hoặc bằng script của bạn; CLI chỉ bắt đầu có ích khi đã có các id ấy. - Nó không ghi
sb.json, cùng lý do trên. - Không có
sb login.SB_TOKENnằm trong môi trường của bạn, và công cụ không lưu credential ở bất cứ đâu. sb devkhông phải hot reload, và không phiên bản tương lai nào của nó có thể là hot reload chừng nào code island còn được phục vụ từ object store của nền tảng.sb deploykhông nguyên tử, một cách có chủ đích.- Chưa có trên npm.
Xử lý sự cố
| Hiện tượng | Nguyên nhân | Cách xử lý |
| --- | --- | --- |
| SB_TOKEN is not set | dev/deploy cần token của tổ chức | export SB_TOKEN=…, hoặc đặt ngay trước lệnh |
| No sb.json here. | Chạy sai thư mục | cd về đúng thư mục chứa sb.json |
| sb.json is missing "versionId". | Thiếu hoặc để rỗng một khoá bắt buộc | Điền đủ cả năm khoá bắt buộc |
| unknown command: … | Gõ nhầm, hoặc dist/ đã cũ | npm run build --workspace cli |
| --port must be a number | --port nhận giá trị không phải số dương | Truyền một số nguyên dương |
| Bị từ chối với not_draft | Version đã rời draft; việc duyệt ghim payload lại | Tạo version draft mới và trỏ sb.json vào đó |
| Bị từ chối với island_not_found | Version chưa khai báo island đó | Khai báo trước — sb deploy với mảng islands làm đúng thứ tự này |
| --submit bị từ chối vì URL loopback | version.embedUrl / version.redirectUri vẫn trỏ localhost | Đặt URL https public, sb deploy lại, rồi submit |
| Island đã đổi mà trang không đổi | sb dev không phải hot reload | Refresh trang |
| sb báo một flag trang này không liệt kê | Bạn đang chạy dist/ cũ | Build lại |
Phát triển chính CLI này
npm run typecheck --prefix cli # tsc trên bin, src VÀ tests
npm test --prefix cli # build trước, rồi vitestnpm test build trước khi chạy, một cách có chủ đích: các suite unit import
src/, còn mọi test spawn tiến trình đều chạy dist/, nên thứ được chứng minh
đúng là thứ được ship.
Hai bộ test giữ cho tài liệu trung thực thay vì tin vào nó:
tests/docs.test.tstrích các lệnh và flag từ chính bytes của trang/cli-docsrồi chạy chúng với binary đã build.tests/readme.test.tslàm điều tương tự cho file này và choREADME.md, đồng thời ràng hai bản README vào nhau — cùng lệnh, cùng flag, cùng biến môi trường, cùng khoásb.json, cùng thứ tự deploy. Một bản dịch trôi lệch sẽ làm hỏng build.
Giấy phép
MIT © Store Builder
