@easytwin/devkit 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "@easytwin/devkit",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "EasyTwin DevKit 核心:lib(函数库)+ bin(easytwin 命令)双导出",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "files": [
8
8
  "dist",
9
9
  "skills",
10
- "scene.example.json"
10
+ "scene.example.json",
11
+ "!**/*.map"
11
12
  ],
12
13
  "exports": {
13
14
  ".": {
@@ -28,6 +29,7 @@
28
29
  "devDependencies": {
29
30
  "@types/node": "^22.10.2",
30
31
  "tsup": "^8.3.5",
32
+ "esbuild": "^0.24.2",
31
33
  "typescript": "^5.7.2",
32
34
  "vitest": "^2.1.8",
33
35
  "three": "0.183.0",
@@ -53,7 +55,7 @@
53
55
  "registry": "https://registry.npmjs.org/"
54
56
  },
55
57
  "scripts": {
56
- "build": "tsup && node scripts/copy-runtime-types.mjs",
58
+ "build": "tsup && node scripts/copy-runtime-types.mjs && node scripts/bundle-runtime.mjs",
57
59
  "test": "vitest run",
58
60
  "typecheck": "tsc --noEmit"
59
61
  }
@@ -16,14 +16,11 @@ description: 当用户要开始使用 EasyTwin DevKit、配置应用级凭证(Ap
16
16
  "env": "可选,prod=正式 / test=测试,缺省 prod",
17
17
  "baseUrl": "可选,缺省按 env 选官方域名",
18
18
  "ossUrl": "可选,缺省官方 OSS",
19
- "opAccountId": "可选,过渡期网关头 op-account-id;env=test 缺省 25",
20
- "opUserId": "可选,过渡期网关头 op-user-id;env=test 缺省 25",
21
- "spaceId": "可选,过渡期网关头 space-id;env=test 缺省 54",
22
19
  "scenes": "可选,由 easytwin scene list 写入的场景摘要,不含 JSON 本体"
23
20
  }
24
21
  ```
25
22
 
26
- - 环境变量 `EASYTWIN_APP_ID` / `EASYTWIN_APP_SECRET` / `EASYTWIN_BASE_URL` / `EASYTWIN_ENV` / `EASYTWIN_OSS_URL` 优先于文件(CI 与不落盘场景)。过渡期另有 `EASYTWIN_OP_ACCOUNT_ID` / `EASYTWIN_OP_USER_ID` / `EASYTWIN_SPACE_ID`(当前测试后端走 OP 网关,免网关接口到位后删除)。
23
+ - 环境变量 `EASYTWIN_APP_ID` / `EASYTWIN_APP_SECRET` / `EASYTWIN_BASE_URL` / `EASYTWIN_ENV` / `EASYTWIN_OSS_URL` 优先于文件(CI 与不落盘场景)。请求带 `x-app-id`(App ID)与 `x-app-secret`(App Secret),不走 OP 网关。
27
24
  - 官方 API:正式 `http://saas-twin.k8s.dtstack.cn/`、测试 `http://172.16.125.3:10100/`;缺省按 env 选择,`EASYTWIN_BASE_URL` 始终优先。
28
25
  - 官方 OSS:`https://dt-easyv-test.oss-cn-hangzhou.aliyuncs.com/`,供 twin runtime 加载 webp wasm / draco / 组件脚本;`EASYTWIN_OSS_URL` 始终优先。
29
26
  - 该文件含密钥,**永不入库**:`init` 已负责 gitignore,不要把它提交进版本库。
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: easytwin-develop
3
- description: 当用户开始 EasyTwin 第三方应用开发、需要了解整体开发工作流(配置凭证→拉工作区代码→拉场景→写 TwinApp 子类→上传)、或询问"接下来该做什么/从哪开始"时使用。其余 EasyTwin 技能的总纲。
3
+ description: 当用户开始 EasyTwin 第三方应用开发、需要了解整体开发工作流(配置凭证→拉工作区代码→拉场景→写 TwinApp 子类→预览测试→上传)、或询问"接下来该做什么/从哪开始"时使用。其余 EasyTwin 技能的总纲。
4
4
  ---
5
5
 
6
6
  # EasyTwin 开发工作流
@@ -10,8 +10,9 @@ EasyTwin DevKit 是 EasyTwin 第三方应用开发工具集。典型工作流:
10
10
  1. **配置凭证**:运行 `easytwin init` 生成项目级 `easytwin.config.json`(App ID / App Secret)。详见 `easytwin-bootstrap`。
11
11
  2. **拉取工作区代码**:`easytwin pull` 从服务端下载代码;远端为空则写入默认 TwinApp `src/main.ts`;本地已有文件时先 diff,默认只补缺失。详见 `easytwin-upload`。
12
12
  3. **拉取场景**:`easytwin scene list` 列出场景,`easytwin scene pull <id>` 拉取场景 JSON 到本地,作为开发参考与预览输入。详见 `easytwin-scene`。
13
- 4. **编写渲染功能**:在工作区 `src/main.ts` 写 TwinApp 子类(`import { TwinApp } from "@easytwin/apps"`,`export default class App extends TwinApp { init; onSceneLoaded; onUpdate; ... }`)。类型由 `easytwin skills sync` 分发。编译用 `easytwin bundle`,运行用插件预览页底部 Run。可上传子集只用 runtime 五件套导出;整包 API 仅本地预览。引擎内核见 `easytwin-core`。详见 `easytwin-render`(主力技能)。
14
- 5. **上传产物**:`easytwin upload <dir>` 全量覆盖上传到服务端,不可逆。详见 `easytwin-upload`。
13
+ 4. **编写渲染功能**:在工作区 `src/main.ts` 写 TwinApp 子类(`import { TwinApp } from "@easytwin/apps"`,`export default class App extends TwinApp { init; onSceneLoaded; onUpdate; ... }`)。类型由 `easytwin skills sync` 分发。编译用 `easytwin bundle`,运行用 `easytwin preview`(浏览器打开打印的 URL,点 Run)或插件预览页底部 Run。可上传子集只用 runtime 五件套导出;整包 API 仅本地预览。引擎内核见 `easytwin-core`。详见 `easytwin-render`(主力技能)。
14
+ 5. **预览测试**:在 `*.spec.ts` 里导出函数,`easytwin preview` 或插件预览页为每个导出生成按钮或输入框。测试文件不同步到服务端。详见 `easytwin-test`。
15
+ 6. **上传产物**:`easytwin upload` 对照远端区分新增/更新/删除,使服务端与本地工作区一致(跳过插件产物与 `*.spec.ts`,不可逆)。详见 `easytwin-upload`。
15
16
 
16
17
  ## 何时读哪个技能
17
18
 
@@ -20,6 +21,7 @@ EasyTwin DevKit 是 EasyTwin 第三方应用开发工具集。典型工作流:
20
21
  | 初始化 / 校验凭证 | easytwin-bootstrap |
21
22
  | 列场景、拉场景 JSON | easytwin-scene |
22
23
  | 用 twin runtime 写渲染 | easytwin-render |
24
+ | 给预览加可点击测试 / 写 `.spec.ts` | easytwin-test |
23
25
  | 理解引擎内核 / 基类 / 相机 / 物理 | easytwin-core |
24
26
  | 上传 / 拉取工作区代码 | easytwin-upload |
25
27
 
@@ -27,4 +29,4 @@ EasyTwin DevKit 是 EasyTwin 第三方应用开发工具集。典型工作流:
27
29
 
28
30
  ## 本地测试
29
31
 
30
- 没有凭据或不想动服务端时,把 `appId` / `appSecret` 都设为 `test` 即进入本地测试模式:`scene list` / `scene pull` 读 devkit 包内的 `scene.example.json`,`upload` 只做本地空跑,`pull` 按远端为空写入默认 `src/main.ts`(已有则不覆盖),这三条不发服务端请求。插件里的场景预览仍是三维场景页(打包的 twin runtime + 本地示例 JSON),系统库(draco/basis/webp)与组件脚本按官方 OSS(=ossUrl)在线加载。预览页 Run 在 mock 下同样可用。
32
+ 没有凭据或不想动服务端时,把 `appId` / `appSecret` 都设为 `test` 即进入本地测试模式:`scene list` / `scene pull` 读 devkit 包内的 `scene.example.json`,`upload` 只做本地空跑,`pull` 按远端为空写入默认 `src/main.ts`(已有则不覆盖),这三条不发服务端请求。`easytwin preview` 与插件预览仍是三维场景页(打包的 twin runtime + 本地示例 JSON),系统库(draco/basis/webp)与组件脚本按官方 OSS(=ossUrl)在线加载。预览页 Run 在 mock 下同样可用。
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  name: easytwin-render
3
- description: 当用户要用 EasyTwin twin runtime 编写渲染功能(写 src/main.ts TwinApp 子类、class extends TwinApp、预览页 Run、easytwin bundle、加载场景、开发自定义组件、调用渲染 API)时使用。这是 EasyTwin 渲染开发的唯一入口技能。
3
+ description: 当用户要用 EasyTwin twin runtime 编写渲染功能(写 src/main.ts TwinApp 子类、class extends TwinApp、easytwin preview、预览页 Run、easytwin bundle、加载场景、开发自定义组件、调用渲染 API)时使用。这是 EasyTwin 渲染开发的唯一入口技能。
4
4
  ---
5
5
 
6
6
  # 用 twin runtime 开发渲染功能
7
7
 
8
- `@easytwin/runtime` 是商业包,公网 npm 不存在,类型由 `easytwin skills sync` 写入 `.easytwin/types/`。`TwinApp` 从 `@easytwin/apps` 引入(同样由 sync 分发)。运行走预览页底部 **Run**,或 `easytwin bundle` 做编译校验。
8
+ `@easytwin/runtime` 是商业包,公网 npm 不存在,类型由 `easytwin skills sync` 写入 `.easytwin/types/`。`TwinApp` 从 `@easytwin/apps` 引入(同样由 sync 分发)。运行走 `easytwin preview` 打开的预览页底部 **Run**,或插件预览,或 `easytwin bundle` 做编译校验。
9
9
 
10
10
  ## 编写 TwinApp 子类(可上传子集)
11
11
 
@@ -66,7 +66,7 @@ export default class App extends TwinApp {
66
66
 
67
67
  1. 若还没有类型:运行 `easytwin skills sync`(无 tsconfig 会生成最小配置,含 `@easytwin/runtime` 与 `@easytwin/apps` 的 paths;已有 tsconfig 时按输出片段自行添加)。
68
68
  2. 编译校验:`easytwin bundle`。lodash 等外部依赖会报错。超出在线 stub 的 runtime import 只 warning。
69
- 3. 运行:打开插件场景预览(先看到三维场景),点底部 **Run**。再点 Run 会先 `onBeforeSceneUnload` / `onDispose`,复用同一台引擎。编译失败不打断已渲染场景;mock 下同样可用。
69
+ 3. 运行:`easytwin preview`(缺省用配置里的场景;也可 `easytwin preview <id>`),用浏览器打开打印的 URL,点底部 **Run**。有 EasyTwin 插件时也可打开场景预览再点 Run。再点 Run 会先 `onBeforeSceneUnload` / `onDispose`,复用同一台引擎。编译失败不打断已渲染场景;mock 下同样可用。给当前场景加可点击验证时写 `*.spec.ts`,见 `easytwin-test`。
70
70
 
71
71
  `Component` / `VirtualRootComponent` 等整包 API 本地预览可用,但**不是**可上传子集;需要组件生命周期细节时读 `references/lifecycle-events.md`。
72
72
 
@@ -1,54 +1,57 @@
1
- ---
2
- name: easytwin-scene
3
- description: 当用户要列出场景、查看有哪些场景、或把某个场景的 JSON 拉到本地作为开发参考/预览输入时使用。
4
- ---
5
-
6
- # 场景管理
7
-
8
- - `easytwin scene list`:列出 SDK 应用已关联的场景(id 为 Scene Key),并把摘要(id / name / linkedSceneId / snapshotUrl / defaultLoading)写入项目 `easytwin.config.json` 的 `scenes` 数组(全量覆盖,不含场景 JSON)。
9
- - `easytwin scene pull <id> [--out <path>]`:按 Scene Key(或关联记录 id)拉取场景快照 JSON 到本地,缺省输出 `./<id>.scene.json`。
10
-
11
- ## 场景 JSON 格式
12
-
13
- 场景 JSON 以 easytwin-runtime 的 `SceneJson` 为准(`packages/easytwin-runtime/src/core/interface.ts`),即组件树序列化结构:
14
-
15
- ```ts
16
- type StateJson<T = any> = { id: string; name: string; config: T; using: boolean; rank: number };
17
-
18
- type ComponentJson<S = any, D = any> = {
19
- id: string; // 实体 id,仅用于层级关系
20
- active: boolean; // 世界大纲 config 修改
21
- lock: boolean; // 仅编辑器生效(是否被选中)
22
- collapsed: boolean; // 世界大纲 config 修改
23
- componentId: string; // 组件 id,兼容后端绑定关系
24
- name: string;
25
- type: string;
26
- version: string;
27
- states?: StateJson<S>[];
28
- data?: D;
29
- children: ComponentJson[];
30
- parentId: string | null; // 引擎不直接存储 parentId
31
- };
32
-
33
- interface SceneJson { id: string; name: string; sceneComponent: ComponentJson; }
34
- ```
35
-
36
- `easytwin scene pull` 按服务端原样保存;该结构可直接作为渲染开发参考与本地预览输入(runtime 的 `SceneManager.loadScene` / `importScene` 接受它,详见 easytwin-render 的 references/scene-and-assets.md)。
37
-
38
- ## 本地测试模式(mock)
39
-
40
- 当 `appId` 与 `appSecret` 均为 `test` 时,`scene list` / `scene pull` 不再请求服务端,改读 devkit 包内 `scene.example.json`:
41
-
42
- - 场景 id 取 `objs[].sceneId` 首个非空值(当前示例为 `sceJTHH9yoqFyRyS9`),场景名固定为「本地示例场景」;
43
- - `scene pull` 的 id 必须与示例 id 一致,否则报错;
44
- - 命令输出会标注「本地测试模式」。该模式仅用于本地联调,不发网络请求。
45
-
46
- ## 典型用法
47
-
48
- 拉取参考场景,作为渲染开发与本地预览的输入:
49
-
50
- ```bash
51
- easytwin scene list
52
- easytwin scene pull scene-123 --out ./scenes/scene-123.json
53
- ```
54
-
1
+ ---
2
+ name: easytwin-scene
3
+ description: 当用户要列出场景、查看有哪些场景、或把某个场景的 JSON 拉到本地作为开发参考/预览输入时使用。
4
+ ---
5
+
6
+ # 场景管理
7
+
8
+ - `easytwin scene list`:列出 SDK 应用已关联的场景(id 为 Scene Key),并把摘要(id / name / linkedSceneId / snapshotUrl / defaultLoading)写入项目 `easytwin.config.json` 的 `scenes` 数组(全量覆盖,不含场景 JSON)。
9
+ - `easytwin scene pull <id> [--out <path>]`:按 Scene Key(或关联记录 id)拉取场景快照 JSON 到本地,缺省输出 `./<id>.scene.json`。
10
+
11
+ ## 场景 JSON 格式
12
+
13
+ 场景 JSON 以 easytwin-runtime 的 `SceneJson` 为准(`packages/easytwin-runtime/src/core/interface.ts`),即组件树序列化结构:
14
+
15
+ ```ts
16
+ type StateJson<T = any> = { id: string; name: string; config: T; using: boolean; rank: number };
17
+
18
+ type ComponentJson<S = any, D = any> = {
19
+ id: string; // 实体 id,仅用于层级关系
20
+ active: boolean; // 世界大纲 config 修改
21
+ lock: boolean; // 仅编辑器生效(是否被选中)
22
+ collapsed: boolean; // 世界大纲 config 修改
23
+ componentId: string; // 组件 id,兼容后端绑定关系
24
+ name: string;
25
+ type: string;
26
+ version: string;
27
+ states?: StateJson<S>[];
28
+ data?: D;
29
+ children: ComponentJson[];
30
+ parentId: string | null; // 引擎不直接存储 parentId
31
+ };
32
+
33
+ interface SceneJson { id: string; name: string; sceneComponent: ComponentJson; }
34
+ ```
35
+
36
+ `easytwin scene pull` 按服务端原样保存;该结构可直接作为渲染开发参考与本地预览输入(runtime 的 `SceneManager.loadScene` / `importScene` 接受它,详见 easytwin-render 的 references/scene-and-assets.md)。
37
+
38
+ ## 本地测试模式(mock)
39
+
40
+ 当 `appId` 与 `appSecret` 均为 `test` 时,`scene list` / `scene pull` 不再请求服务端,改读 devkit 包内 `scene.example.json`:
41
+
42
+ - 场景 id 取 `objs[].sceneId` 首个非空值(当前示例为 `sceJTHH9yoqFyRyS9`),场景名固定为「本地示例场景」;
43
+ - `scene pull` 的 id 必须与示例 id 一致,否则报错;
44
+ - 命令输出会标注「本地测试模式」。该模式仅用于本地联调,不发网络请求。
45
+
46
+ ## 典型用法
47
+
48
+ 拉取参考场景,作为渲染开发与本地预览的输入:
49
+
50
+ ```bash
51
+ easytwin scene list
52
+ easytwin preview
53
+ easytwin scene pull scene-123 --out ./scenes/scene-123.json
54
+ ```
55
+
56
+ `easytwin preview [sceneId]` 起本地预览页(三维 + Run + 测试控件),用浏览器打开打印的 URL。缺省 sceneId 取配置 `scenes` 里 `defaultLoading` 否则首项。
57
+
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: easytwin-test
3
+ description: 当用户要写预览测试、工作区 .spec.ts、给场景预览加测试按钮或输入框、或确认测试文件会不会被上传时使用。
4
+ ---
5
+
6
+ # 预览测试:`*.spec.ts`
7
+
8
+ 工作区任意目录的 `*.spec.ts`(建议 `src/*.spec.ts`)只给**本地场景预览**用。`easytwin preview` 或插件打开预览时扫描命名导出的函数,每个导出一个控件;点击后用当前 `TwinAppContext` 调用。这些文件留在本地,`easytwin upload` / `pull` 都跳过。
9
+
10
+ ## 导出约定
11
+
12
+ - 只认 **named export 的函数**( `export function` / `export async function` / `export const name = …` )。不认 `export default`、class、type、再导出。
13
+ - 第一个参数是 `TwinAppContext`(与 TwinApp 钩子同一份 ctx:引擎、场景、camera、`assets`、`logger`、`cleanup` / `sceneCleanup`)。
14
+ - **只有 ctx**:预览显示按钮,文案=函数名。
15
+ - **ctx 后再跟参数**:预览显示输入框+执行,把输入框字符串当作第二个参数传入。
16
+
17
+ ```ts
18
+ import type { TwinAppContext } from "@easytwin/apps";
19
+ import { THREE } from "@easytwin/runtime";
20
+
21
+ export async function addBox(ctx: TwinAppContext) {
22
+ const mesh = new THREE.Mesh(
23
+ new THREE.BoxGeometry(1, 1, 1),
24
+ new THREE.MeshBasicMaterial({ color: 0xff6644 }),
25
+ );
26
+ ctx.scene?.add(mesh);
27
+ ctx.sceneCleanup(() => {
28
+ ctx.scene?.remove(mesh);
29
+ });
30
+ }
31
+
32
+ export async function logName(ctx: TwinAppContext, name: string) {
33
+ ctx.logger.info("test", name);
34
+ }
35
+ ```
36
+
37
+ 依赖白名单与 TwinApp 相同:只允许 `@easytwin/runtime`、`@easytwin/apps` 和相对路径。通过 `ctx` 操作**当前预览已加载的场景**;不要 `RuntimeEngine.create`,不要 `import` `src/main.ts`(会打进另一份应用,不是正在跑的那份)。加到场景里的物体用 `ctx.sceneCleanup` 对称拆除。
38
+
39
+ 可上传的 runtime 导出名仍是五件套;超出的名字本地预览能跑,上传在线 TwinApp 可能编不过(测试文件本身不会被上传)。
40
+
41
+ ## 怎么跑
42
+
43
+ 1. 把 spec 写到工作区(文件名以 `.spec.ts` 结尾)。
44
+ 2. 运行 `easytwin preview`(或打开插件场景预览):场景加载后,Run 栏上方出现测试控件;点 **刷新测试** 可重扫而不关预览。
45
+ 3. 点按钮或填输入框再执行。不必先点 Run;若已 Run,测试与 TwinApp 共用同一台引擎。
46
+
47
+ 完成标准:预览里能看到每个导出对应的控件,点下去函数被调用,且 `easytwin upload` 的文件列表里没有该 `.spec.ts`。
@@ -1,38 +1,39 @@
1
- ---
2
- name: easytwin-upload
3
- description: 当用户要把本地开发产物目录上传回 EasyTwin、从服务端拉取工作区代码、远端没有代码时创建默认文件、或询问上传是否可逆/与本地 diff 如何同步时使用。
4
- ---
5
-
6
- # 工作区代码:拉取与全量上传
7
-
8
- 上传与拉取共用同一套工作区代码接口,方向相反。
9
-
10
- ## 拉取:`easytwin pull [dir]`
11
-
12
- 从服务端下载 App ID 对应的工作区文件到 `[dir]`(缺省当前目录)。
13
-
14
- - 远端为空:本地没有 `src/main.ts` 时写入默认 TwinApp 子类入口;本地已有入口则不覆盖。
15
- - 远端有文件:与本地做 diff。`identical` 跳过,`remote-only` 写入,`local-only` 保留,**内容冲突默认不覆盖**。
16
- - `--force` 才用远端内容覆盖冲突文件;即使 `--force` 也不删除仅本地文件。
17
- - `--dry-run` 只打印 diff,不写盘。
18
- - 本地测试模式(`appId`/`appSecret` 均为 `test`):不发请求,按远端为空处理。
19
-
20
- 有冲突时先看摘要,确认要用远端覆盖再加 `--force`。插件里可对冲突文件打开「远程 ↔ 本地」diff。
21
-
22
- ## 上传:`easytwin upload <dir>`
23
-
24
- `<dir>` 下的全部文件上传,按 App ID 决定的目标空间**全量覆盖**。
25
-
26
- > ⚠️ **覆盖不可逆**:上传会覆盖服务端同名内容,没有本地清单与回滚机制。上传前务必确认目录内容与目标空间。
27
-
28
- - 递归收集 `<dir>` 下所有文件(默认跳过 `.git`)。
29
- - 按工作区代码文件接口全量覆盖:先拉远端文件列表与约束(扩展名/数量/目录深度),再删除远端多余文件、批量更新变更、逐个新建缺失文件。
30
- - 上传目标空间由凭据(App ID)决定;场景 pull 与工作区 pull/upload 是独立能力,互不引用。
31
-
32
- 当 `appId` 与 `appSecret` 均为 `test` 时,`easytwin upload <dir>` 退化为本地空跑(dry-run):只收集与统计文件、不上传,进度照常上报,输出标注「本地测试模式」。
33
-
34
- ## 步骤
35
-
36
- 1. 新项目或本地还没有代码:`easytwin pull`(远端空则得到默认 `src/main.ts`)。
37
- 2. 本地已有改动且可能与远端不同:`easytwin pull --dry-run` 看 diff,再决定是否 `--force`。
38
- 3. 确认目标空间(App ID)与目录内容后,`easytwin upload <dir>` 上传(不可逆)。
1
+ ---
2
+ name: easytwin-upload
3
+ description: 当用户要把本地工作区上传回 EasyTwin、从服务端拉取工作区代码、远端没有代码时创建默认文件、或询问上传是否可逆/与本地 diff 如何同步时使用。
4
+ ---
5
+
6
+ # 工作区代码:拉取与全量上传
7
+
8
+ 上传与拉取共用同一套工作区代码接口,方向相反。
9
+
10
+ ## 拉取:`easytwin pull [dir]`
11
+
12
+ 从服务端下载 App ID 对应的工作区文件到 `[dir]`(缺省当前目录)。
13
+
14
+ - 远端为空:本地没有 `src/main.ts` 时写入默认 TwinApp 子类入口;本地已有入口则不覆盖。
15
+ - 远端有文件:与本地做 diff。`identical` 跳过,`remote-only` 写入,`local-only` 保留,**内容冲突默认不覆盖**。
16
+ - `--force` 才用远端内容覆盖冲突文件;即使 `--force` 也不删除仅本地文件。
17
+ - `--dry-run` 只打印 diff,不写盘。
18
+ - 本地测试模式(`appId`/`appSecret` 均为 `test`):不发请求,按远端为空处理。
19
+
20
+ 有冲突时先看摘要,确认要用远端覆盖再加 `--force`。插件里可对冲突文件打开「远程 ↔ 本地」diff。
21
+
22
+ ## 上传:`easytwin upload [dir]`
23
+
24
+ 把工作区(缺省当前目录)上传,使服务端与本地一致(全量对齐,不可逆)。**不选目录**:插件按钮与缺省 CLI 都以项目根为准。
25
+
26
+ > ⚠️ **覆盖不可逆**:远端多余文件会删除,同名内容会更新,没有本地清单与回滚机制。
27
+
28
+ - 递归收集目录下文件,默认只处理 `.ts` / `.tsx` / `.js` / `.json`;并跳过插件产物与相关路径:`.git` / `node_modules` / `dist` / `.easytwin` / `.cursor` / `.claude` / `.qoder` / `.vscode`、`easytwin.config.json`、`.gitignore`、`tsconfig.json` / `tsconfig.*.json`、`*.scene.json`、`*.spec.ts`(预览测试,见 easytwin-test)。
29
+ - 先 `POST .../share/sdk-application-code/pull` 拉远端文件,本地 diff 后一次 `POST .../share/sdk-application-code/push`(`create`/`update`/`delete`);内容相同跳过、无变更不发 push。不请求仍走 OP 网关的 `workspace-config`。
30
+ - 上传目标空间由凭据(`x-app-id` + `x-app-secret`)决定;场景 pull 与工作区 pull/upload 是独立能力,互不引用。
31
+ - 认证头 `x-app-id` = App ID、`x-app-secret` = App Secret,不走 OP 网关。
32
+
33
+ 当 `appId` 与 `appSecret` 均为 `test` 时,`easytwin upload` 退化为本地空跑(dry-run):只收集与统计文件、不上传,进度照常上报,输出标注「本地测试模式」。
34
+
35
+ ## 步骤
36
+
37
+ 1. 新项目或本地还没有代码:`easytwin pull`(远端空则得到默认 `src/main.ts`)。
38
+ 2. 本地已有改动且可能与远端不同:`easytwin pull --dry-run` 看 diff,再决定是否 `--force`。
39
+ 3. 确认目标空间(App ID)后,`easytwin upload` 上传工作区(不可逆;不含插件产物与 `*.spec.ts`)。