Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/source/guideline/cloudbase/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ Prefer long-term memory when available: write the scenarios and working rules th
- Refer to the `web-development` skill for deployment process
- `uploadFiles` is for static hosting only; if the task needs a COS object that must be queried or polled with the storage SDK, use `manageStorage` / `queryStorage`
- Remind users that CDN has a few minutes of cache after deployment
- **Subdirectory deployment**: when deploying to a subdirectory (e.g., `/vite-test`), `base`/`publicPath`/`assetPrefix` must be set to the absolute subdirectory path (e.g., `'/vite-test/'`), NOT `'./'` or empty. After changing the build config, rebuild and verify asset paths in the output before calling `uploadFiles`. The `cloudPath` parameter must not start with `/` (use `'vite-test'`, not `'/vite-test'`).

**Backend Deployment:**
- **Cloud Functions**: Refer to the `cloud-functions` skill - Runtime cannot be changed after creation, must select correct runtime initially
Expand Down
16 changes: 15 additions & 1 deletion config/source/skills/web-development/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,10 +128,24 @@ Use this section only when the Web project needs CloudBase platform features.
### Static hosting defaults

- Build before deployment
- Prefer relative asset paths for static hosting compatibility
- Deploy the entire `dist/` directory contents, not just `index.html`
- Use hash routing by default when the project lacks server-side route rewrites
- If the user does not specify a root path, avoid deploying directly to the site root by default

### Subdirectory deployment checklist

When deploying to a subdirectory (e.g., `/vite-test`), the following must be verified **before calling `uploadFiles`**. If any item fails, stop and fix it first:

1. **Build config `base`/`publicPath`/`assetPrefix` must be set to the absolute subdirectory path** (e.g., deploy target `/vite-test` → Vite `base: '/vite-test/'`, Webpack `publicPath: '/vite-test/'`). **Forbidden**: `'./'`, empty string, or `'/'` — these cause 404 for assets when the access URL lacks a trailing slash.
2. **Rebuild after changing build config** — modifying `base`/`publicPath` without rebuilding leaves stale references in `dist/`.
3. **Verify build output** — check that asset references in `dist/index.html` use the subdirectory prefix (e.g., `src="/vite-test/assets/..."`), not root-relative paths (`src="/assets/..."`).

`uploadFiles` `cloudPath` format: relative to hosting root, **no leading `/`**. Example: `'vite-test'` (not `'/vite-test'`). The `localPath` should point to the `dist/` directory.

### Root deployment

When deploying to the site root (`/`), `cloudPath` can be omitted or left empty. For Vite projects, `base: '/'` (the default) works correctly.

### CloudBase quick start

```js
Expand Down
12 changes: 9 additions & 3 deletions doc/mcp-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
<tr><td><code>modifyDataModel</code></td><td>基于Mermaid classDiagram创建数据模型。为保持兼容性,工具名仍为 modifyDataModel;当前仅支持创建新模型,不支持更新现有模型结构。内置异步任务监控,自动轮询直至完成或超时。</td></tr>
<tr><td><code>queryFunctions</code></td><td>函数域统一只读入口。通过更自解释的 action 查询函数列表、函数详情、日志、层、触发器和代码下载地址。</td></tr>
<tr><td><code>manageFunctions</code></td><td>函数域统一写入口。通过 action 管理函数创建、代码更新、配置更新、调用函数、触发器和层绑定。危险操作需要显式 confirm=true。</td></tr>
<tr><td><code>uploadFiles</code></td><td>上传文件到静态网站托管,仅用于 Web 站点部署,不用于云存储对象上传。部署前请先完成构建;如果站点会部署到子路径,请检查构建配置中的 publicPath、base、assetPrefix 等是否使用相对路径,避免静态资源加载失败。若需要上传 COS 云存储文件,请使用 manageStorage。对于本地评测、现有脚手架补全或仅需本地开发服务器验证的任务,通常不需要调用此工具,除非用户明确要求部署站点。</td></tr>
<tr><td><code>uploadFiles</code></td><td>上传文件到静态网站托管,仅用于 Web 站点部署,不用于云存储对象上传。部署前请先完成构建。若需要上传 COS 云存储文件,请使用 manageStorage。对于本地评测、现有脚手架补全或仅需本地开发服务器验证的任务,通常不需要调用此工具,除非用户明确要求部署站点。⚠️ 子目录部署必检项(部署到非根路径时,调用 uploadFiles 前必须逐项确认):1. 构建配置中的 base/publicPath/assetPrefix 必须设为与部署目标一致的绝对路径(如部署到 /vite-test,则 base 设为 '/vite-test/',带前导和尾部斜杠)。禁止使用 './' 或空字符串,否则子目录部署后静态资源会因路径解析错误而 404。2. 修改构建配置后必须重新 build。3. 必须验证构建产物中的资源引用路径已更新为非绝对根路径 '/'。任何一项未通过,禁止调用 uploadFiles。</td></tr>
<tr><td><code>deleteFiles</code></td><td>删除静态网站托管的文件或文件夹</td></tr>
<tr><td><code>findFiles</code></td><td>搜索静态网站托管的文件</td></tr>
<tr><td><code>domainManagement</code></td><td>统一的域名管理工具,支持绑定、解绑、查询和修改域名配置</td></tr>
Expand Down Expand Up @@ -418,15 +418,21 @@ classDiagram
---

### `uploadFiles`
上传文件到静态网站托管,仅用于 Web 站点部署,不用于云存储对象上传。部署前请先完成构建;如果站点会部署到子路径,请检查构建配置中的 publicPath、base、assetPrefix 等是否使用相对路径,避免静态资源加载失败。若需要上传 COS 云存储文件,请使用 manageStorage。对于本地评测、现有脚手架补全或仅需本地开发服务器验证的任务,通常不需要调用此工具,除非用户明确要求部署站点。
上传文件到静态网站托管,仅用于 Web 站点部署,不用于云存储对象上传。部署前请先完成构建。若需要上传 COS 云存储文件,请使用 manageStorage。对于本地评测、现有脚手架补全或仅需本地开发服务器验证的任务,通常不需要调用此工具,除非用户明确要求部署站点。

⚠️ 子目录部署必检项(部署到非根路径时,调用 uploadFiles 前必须逐项确认):
1. 构建配置中的 base/publicPath/assetPrefix 必须设为与部署目标一致的绝对路径(如部署到 /vite-test,则 base 设为 '/vite-test/',带前导和尾部斜杠)。禁止使用 './' 或空字符串,否则子目录部署后静态资源会因路径解析错误而 404。
2. 修改构建配置后必须重新 build。
3. 必须验证构建产物中的资源引用路径已更新为非绝对根路径 '/'。
任何一项未通过,禁止调用 uploadFiles。

#### 参数

<table>
<thead><tr><th>参数名</th><th>类型</th><th>必填</th><th>说明</th></tr></thead>
<tbody>
<tr><td><code>localPath</code></td><td>string</td><td></td><td>本地文件或文件夹路径,需要是绝对路径,例如 /tmp/files/data.txt。</td></tr>
<tr><td><code>cloudPath</code></td><td>string</td><td></td><td>静态托管云端文件或文件夹路径,例如 files/data.txt。若部署到子路径,请同时检查构建配置中的 publicPath、base、assetPrefix 等是否为相对路径。云存储对象路径请改用 manageStorage。</td></tr>
<tr><td><code>cloudPath</code></td><td>string</td><td></td><td>静态托管云端路径,相对于托管根目录,不要带前导 '/'。例如 'vite-test' 表示部署到 /vite-test/ 子目录,而非 '/vite-test'。若上传整个 dist 目录到子目录,cloudPath 填子目录名(如 'vite-test'),localPath 填 dist 绝对路径。云存储对象路径请改用 manageStorage。</td></tr>
<tr><td><code>files</code></td><td>array of object</td><td></td><td>多文件上传配置 默认值: []</td></tr>
<tr><td><code>files[].localPath</code></td><td>string</td><td>是</td><td></td></tr>
<tr><td><code>files[].cloudPath</code></td><td>string</td><td>是</td><td></td></tr>
Expand Down
3 changes: 2 additions & 1 deletion doc/prompts/auth-tool.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ Recommended MCP request:
```json
{
"success": true,
"envId": "env-xxx",
"envId": "your-full-env-id",
"loginMethods": {
"usernamePassword": true,
"email": true,
Expand Down Expand Up @@ -173,6 +173,7 @@ Parameter mapping for downstream Web auth code:
- `UserNameLogin` also enables the broader password-login surface exposed by `auth.signInWithPassword({ username|email|phone, password })`
- `SmsVerificationConfig.Type = "apis"` requires both `Name` and `Method`
- `EnvId` is always the CloudBase environment ID, not the publishable key
- If the conversation only contains an environment alias, nickname, or other shorthand, resolve it to the canonical full `EnvId` first before generating auth config, SDK init examples, or console links

Internal behavior of `manageAppAuth(action="patchLoginStrategy")`:

Expand Down
62 changes: 44 additions & 18 deletions doc/prompts/auth-web.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ Keep local `references/...` paths for files that ship with the current skill dir
- Using `signInWithEmailAndPassword` or `signUpWithEmailAndPassword` for username-style accounts such as `admin` and `editor`.
- Keeping the login or register account input as `type="email"` when the task explicitly says the account identifier is a plain username string.
- Starting implementation before calling `queryAppAuth(action="getLoginConfig")` and enabling `usernamePassword` when it is still off.
- **Treating `auth.getUser()` returning a user as proof of real login.** When the SDK is initialized with a `publishableKey` / `accessKey`, it may silently create an anonymous session. A route guard's `checkAuth()` must verify that the user actually signed in with username/password (e.g. check `session.loginType !== 'ANONYMOUS'` or that `user.user_metadata?.username` exists), not just that `getUser()` returns non-null. Otherwise unauthenticated visitors pass the guard, protected pages render without a real user, and role-based UI (edit / delete buttons gated on `currentUser.role`) breaks because `currentUser` has no role record.

## Overview

Expand All @@ -95,9 +96,8 @@ Keep local `references/...` paths for files that ship with the current skill dir

**Use Case**: Web frontend projects using `@cloudbase/js-sdk@2.24.0+` for user authentication
**Key Benefits**: Supabase-like Auth API shape, supports phone, email, anonymous, username/password, and third-party login methods
**Official `@cloudbase/js-sdk` CDN**: `https://static.cloudbase.net/cloudbase-js-sdk/latest/cloudbase.full.js`

Use the same CDN address as `web-development`. Prefer npm installation in modern bundler projects, and use the CDN form for static HTML, no-build demos, or low-friction examples.
Use npm installation for modern Web projects. In React, Vue, Vite, and other bundler-based apps, install and import `@cloudbase/js-sdk` from the project dependencies instead of using a CDN script.

## Prerequisites

Expand All @@ -107,6 +107,7 @@ Use the same CDN address as `web-development`. Prefer npm installation in modern
### Parameter map

- For username-style identifiers, the required precondition is `loginMethods.usernamePassword === true` from `queryAppAuth(action="getLoginConfig")`. If it is false, enable it with `manageAppAuth(action="patchLoginStrategy", patch={ usernamePassword: true })` before wiring frontend auth code.
- If the conversation only provides an environment alias, nickname, or other shorthand, resolve it with `envQuery(action="list", alias=..., aliasExact=true)` first and use the returned canonical full `EnvId` for SDK init, console links, and generated config. Do not pass alias-like short forms directly into `cloudbase.init({ env })`.
- Treat CloudBase Web Auth as **Supabase-like**, not “every `supabase-js` auth example is valid unchanged”
- When `queryAppAuth` / `manageAppAuth` returns `sdkStyle: "supabase-like"` and `sdkHints`, follow those method and parameter hints first
- `auth.signInWithOtp({ phone })` and `auth.signUp({ phone })` use the phone number in a `phone` field, not `phone_number`
Expand All @@ -121,10 +122,11 @@ Use the same CDN address as `web-development`. Prefer npm installation in modern
## Quick Start

```js
// npm install @cloudbase/js-sdk
import cloudbase from '@cloudbase/js-sdk'

const app = cloudbase.init({
env: `env`, // CloudBase environment ID
env: 'your-full-env-id', // Canonical full CloudBase environment ID resolved from envQuery or the console, not an alias or shorthand
region: `region`, // CloudBase environment Region, default 'ap-shanghai'
accessKey: 'publishable key', // required, get from auth-tool-cloudbase
auth: { detectSessionInUrl: true }, // required
Expand All @@ -141,8 +143,9 @@ If the current task has not retrieved a real Publishable Key, omit `accessKey` i

**1. Phone OTP (Recommended)**
- Automatically use `auth-tool-cloudbase` to turn on `SMS Login` through `manageAppAuth`
- For phone registration, send the phone number to `auth.signUp({ phone, ... })` first, then call the returned `verifyOtp({ token })`. Do not swap the order.
```js
const { data, error } = await auth.signInWithOtp({ phone: '13800138000' })
const { data, error } = await auth.signUp({ phone: '13800138000' })
const { data: loginData, error: loginError } = await data.verifyOtp({ token:'123456' })
```

Expand All @@ -154,10 +157,35 @@ const { data: loginData, error: loginError } = await data.verifyOtp({ token: '65
```

**3. Password**

All auth methods return `{ data, error }`. Always check `error` first:
```js
const usernameLogin = await auth.signInWithPassword({ username: 'test_user', password: 'pass123' })
const emailLogin = await auth.signInWithPassword({ email: 'user@example.com', password: 'pass123' })
const phoneLogin = await auth.signInWithPassword({ phone: '13800138000', password: 'pass123' })
// Login — returns { data: { user, session }, error: null } on success
const { data, error } = await auth.signInWithPassword({ username: 'test_user', password: 'pass123' })
if (error) {
// Handle login failure (wrong password, user not found, provider not enabled)
console.error('Login failed:', error.message)
return false
}
// data.user.id is the uid; data.session contains the active session
const uid = data.user.id

// Also works with email or phone:
// await auth.signInWithPassword({ email: 'user@example.com', password: 'pass123' })
// await auth.signInWithPassword({ phone: '13800138000', password: 'pass123' })
```

**Checking login state (for route guards / auth checks):**
```js
// Use auth.getLoginState() to get the current session.
// IMPORTANT: uid alone is NOT enough — when the SDK is initialized with a
// publishableKey it may create an anonymous session that also has a uid.
// Route guards must reject anonymous sessions explicitly.
const loginState = await auth.getLoginState()
const isRealLogin = !!loginState
&& !!loginState.uid
&& loginState.loginType !== 'ANONYMOUS'
// Use isRealLogin (not just !!uid) to gate protected routes.
```

**4. Registration**
Expand All @@ -181,7 +209,7 @@ const emailVerifyResult = await emailSignUp.data.verifyOtp({ token: '123456' })
// Phone Otp
// Use only when the task explicitly requires phone numbers.
// Phone Otp
const phoneSignUp = await auth.signUp({ phone: '13800138000', nickname: 'User' })
const phoneSignUp = await auth.signUp({ phone: '13800138000', password: 'pass123', nickname: 'User' })
const phoneVerifyResult = await phoneSignUp.data.verifyOtp({ token: '123456' })
```

Expand All @@ -200,11 +228,13 @@ const handleRegister = async () => {
}

const handleLogin = async () => {
const { error } = await auth.signInWithPassword({
const { data, error } = await auth.signInWithPassword({
username,
password,
})
if (error) throw error
// Login succeeded — data.user.id is the uid
return true
}
```

Expand All @@ -214,25 +244,21 @@ Do not use email OTP or email-only helpers for these flows unless the task expli
const handleSendCode = async () => {
try {
const { data, error } = await auth.signUp({
email,
name: username || email.split('@')[0],
phone,
password: password || undefined,
})
if (error) throw error
setSignUpData(data)
verifyOtpRef.current = data.verifyOtp
} catch (error) {
console.error('Failed to send sign-up code', error)
}
}

const handleRegister = async () => {
try {
if (!signUpData?.verifyOtp) throw new Error('Please send the code first')
if (!verifyOtpRef.current) throw new Error('Please send the code first')

const { error } = await signUpData.verifyOtp({
email,
token: code,
type: 'signup',
})
const { error } = await verifyOtpRef.current({ token: code })
if (error) throw error
} catch (error) {
console.error('Failed to complete sign-up', error)
Expand Down
Loading
Loading