Skip to content
Closed
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
11 changes: 10 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,19 @@ 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
- 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 (MANDATORY before calling uploadFiles)

When deploying to a subdirectory (e.g., `/vite-test`), the agent MUST verify ALL of the following before calling `uploadFiles`. If any check fails, fix it first — do NOT proceed with upload.

1. **Build config matches deploy path**: The framework's base/publicPath/assetPrefix must be set to the **absolute path** matching the target subdirectory, with both leading and trailing slashes. For example, deploying to `/vite-test` requires `base: '/vite-test/'` in Vite, `publicPath: '/vite-test/'` in webpack. **Forbidden values**: `'./'`, `''`, or any relative path — these cause 404 when the access URL lacks a trailing slash.
2. **Rebuild after config change**: After modifying the build config, a fresh build is mandatory. The old `dist/` does not reflect the new paths.
3. **Verify build output**: Check that the built `index.html` references assets with the configured subdirectory prefix (e.g., `src="/vite-test/assets/main.js"`), not root-relative (`src="/assets/main.js"`) or relative (`src="./assets/main.js"`) paths.
4. **cloudPath format**: The `cloudPath` parameter in `uploadFiles` is relative to the hosting root — do NOT add a leading `/`. For example, use `'vite-test'` not `'/vite-test'`.
5. **Upload the entire dist directory**: Deploy the full `dist/` folder contents, not just `index.html`.

### CloudBase quick start

```js
Expand Down
19 changes: 19 additions & 0 deletions config/source/skills/web-development/frameworks.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,25 @@
- Keep environment-specific values in `.env` or the project's existing config pattern instead of hardcoding them into UI files.
- Check route base paths, asset paths, and build output behavior before deployment.

### Vite subdirectory deployment (CRITICAL)

When deploying to a CloudBase static hosting subdirectory, the `base` config in `vite.config.ts` **must** match the deployment path:

```ts
// vite.config.ts — deploying to /vite-test
export default defineConfig({
base: '/vite-test/', // ✅ Absolute path with leading AND trailing slash
// base: './', // ❌ WRONG — causes 404 when URL has no trailing slash
// base: '', // ❌ WRONG — same problem as './'
})
```

Rules:
- **Always use absolute path** with leading `/` and trailing `/` — e.g., `'/vite-test/'`
- **Never use `'./'`** or empty string for subdirectory deploys — relative paths break when the access URL lacks a trailing slash
- After changing `base`, **always rebuild** (`npm run build`) before uploading
- Verify the built `dist/index.html` contains paths like `/vite-test/assets/...`, not `/assets/...` or `./assets/...`

## Routing and build defaults

- Use the existing router if present; do not switch routing libraries without an explicit requirement.
Expand Down
6 changes: 3 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、baseassetPrefix 等是否使用相对路径,避免静态资源加载失败。若需要上传 COS 云存储文件,请使用 manageStorage。对于本地评测、现有脚手架补全或仅需本地开发服务器验证的任务,通常不需要调用此工具,除非用户明确要求部署站点。</td></tr>
<tr><td><code>uploadFiles</code></td><td>上传文件到静态网站托管,仅用于 Web 站点部署,不用于云存储对象上传。部署前请先完成构建。⚠️ 子目录部署必检项:若站点部署到子路径(如 /vite-test),则构建配置中的 base/publicPath/assetPrefix 必须设为与部署路径一致的绝对路径(如 '/vite-test/',带前导和尾部斜杠),禁止使用 './' 或空字符串,否则访问 URL 不带尾部斜杠时静态资源会 404。设置后必须重新构建,并验证产物中的资源引用路径已更新为非根路径 '/xxx/' 格式。cloudPath 不要带前导 '/',例如 'vite-test' 而非 '/vite-test'。若需要上传 COS 云存储文件,请使用 manageStorage。对于本地评测、现有脚手架补全或仅需本地开发服务器验证的任务,通常不需要调用此工具,除非用户明确要求部署站点。</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,15 @@ classDiagram
---

### `uploadFiles`
上传文件到静态网站托管,仅用于 Web 站点部署,不用于云存储对象上传。部署前请先完成构建;如果站点会部署到子路径,请检查构建配置中的 publicPath、baseassetPrefix 等是否使用相对路径,避免静态资源加载失败。若需要上传 COS 云存储文件,请使用 manageStorage。对于本地评测、现有脚手架补全或仅需本地开发服务器验证的任务,通常不需要调用此工具,除非用户明确要求部署站点。
上传文件到静态网站托管,仅用于 Web 站点部署,不用于云存储对象上传。部署前请先完成构建。⚠️ 子目录部署必检项:若站点部署到子路径(如 /vite-test),则构建配置中的 base/publicPath/assetPrefix 必须设为与部署路径一致的绝对路径(如 '/vite-test/',带前导和尾部斜杠),禁止使用 './' 或空字符串,否则访问 URL 不带尾部斜杠时静态资源会 404。设置后必须重新构建,并验证产物中的资源引用路径已更新为非根路径 '/xxx/' 格式。cloudPath 不要带前导 '/',例如 'vite-test' 而非 '/vite-test'。若需要上传 COS 云存储文件,请使用 manageStorage。对于本地评测、现有脚手架补全或仅需本地开发服务器验证的任务,通常不需要调用此工具,除非用户明确要求部署站点。

#### 参数

<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、baseassetPrefix 等是否为相对路径。云存储对象路径请改用 manageStorage。</td></tr>
<tr><td><code>cloudPath</code></td><td>string</td><td></td><td>静态托管云端路径,相对于托管根目录,不要带前导 '/',例如 'vite-test' 或 'vite-test/assets/main.js'。若部署到子路径,必须确保构建配置中的 base/publicPath/assetPrefix 已设为对应的绝对路径(如 '/vite-test/')。云存储对象路径请改用 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