npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sbuilder/cli

v0.1.1

Published

Scaffold, run and deploy an app for the Store Builder platform.

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ì

sbmộ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 --help

Từ đâ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ộ khungsb 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
  1. Tạo app trong tổ chức của bạn, ghi lại clientId / clientSecret mà nền tảng trả về. Secret chỉ hiện một lần duy nhất.
  2. Tạo một version DRAFT trỏ vào URL local — embed URL http://localhost:4000/embed, redirect URI http://localhost:4000/oauth/callback. Không cần tunnel: một version draft được phép trỏ vào http://localhost và 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 --submit

sb init — tạo khung một app chạy được

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

Ghi 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_URIPORT | | 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.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à devdeploy đọ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 dev

Theo 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 --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ế. 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_APIPORT.


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 init khô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_TOKEN nằm trong môi trường của bạn, và công cụ không lưu credential ở bất cứ đâu.
  • sb dev khô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 deploy khô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 vitest

npm 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.ts trích các lệnh và flag từ chính bytes của trang /cli-docs rồi chạy chúng với binary đã build.
  • tests/readme.test.ts làm điều tương tự cho file này và cho README.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