@easytwin/devkit 0.1.3 → 0.1.4

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/dist/index.d.ts CHANGED
@@ -1,5 +1,9 @@
1
- /** 配置文件名(项目根)。 */
1
+ /** 用户项目内工具目录(凭据 / 场景快照 / types)。 */
2
+ declare const EASYTWIN_DIR = ".easytwin";
3
+ /** 配置文件名(落在 `.easytwin/` 下,D29)。 */
2
4
  declare const CONFIG_FILE_NAME = "easytwin.config.json";
5
+ /** scene pull 缺省目录(相对 `.easytwin/`)。 */
6
+ declare const EASYTWIN_SCENES_DIRNAME = "scenes";
3
7
  /**
4
8
  * 官方缺省域名(正式环境)。该常量是全仓唯一收口点,只允许写在这里。
5
9
  */
@@ -7,13 +11,19 @@ declare const DEFAULT_BASE_URL = "http://saas-twin.k8s.dtstack.cn/";
7
11
  /** 官方测试环境域名。 */
8
12
  declare const TEST_BASE_URL = "http://172.16.125.3:10100/";
9
13
  /**
10
- * 官方 OSS(twin runtime baseOSSUrl)。系统资产由此拼出:
11
- * `{ossUrl}/easytwin/system/libs/webp-wasm.wasm`、
14
+ * 官方测试 OSStwin runtime baseOSSUrl 缺省用此桶(D11)
15
+ * 系统资产由此拼出:`{ossUrl}/easytwin/system/libs/webp-wasm.wasm`、
12
16
  * `{ossUrl}/easytwin/system/libs/draco/`、
13
17
  * `{ossUrl}/easytwin/system/components/custom/{type}/{version}/script.js`。
14
18
  * 不得用 API baseUrl 代替:场景/上传走 API,系统库与组件脚本走 OSS。
15
19
  */
20
+ declare const TEST_OSS_URL = "https://dt-easyv-test.oss-cn-hangzhou.aliyuncs.com/";
21
+ /** 官方正式 OSS。runtime 类型在 env=prod 时用此桶(D31);资产根缺省仍为测试桶。 */
22
+ declare const PROD_OSS_URL = "https://dt-easyv-prod.oss-cn-hangzhou.aliyuncs.com/";
23
+ /** 资产根缺省 = 测试 OSS(D11)。 */
16
24
  declare const DEFAULT_OSS_URL = "https://dt-easyv-test.oss-cn-hangzhou.aliyuncs.com/";
25
+ /** runtime 类型声明相对 OSS 根的路径(D31)。 */
26
+ declare const RUNTIME_TYPES_RELPATH = "easytwin/system/libs/runtime/types/index.d.ts";
17
27
  /** 服务环境,决定缺省 baseUrl。 */
18
28
  type EasyTwinEnv = "prod" | "test";
19
29
  /** 本地测试模式哨兵凭据:appId 与 appSecret 均为该值时,场景/上传走本地 mock,不发网络请求(见 AGENTS.md 4.4)。 */
@@ -60,6 +70,25 @@ declare class ConfigError extends Error {
60
70
  constructor(message: string);
61
71
  }
62
72
  declare function configFilePath(cwd: string): string;
73
+ /** 拼接 OSS 根与相对路径,两端斜杠归一。 */
74
+ declare function joinOssPath(ossUrl: string, relPath: string): string;
75
+ /**
76
+ * `@easytwin/runtime` 类型 `index.d.ts` 的 OSS 地址(D31)。
77
+ * 显式 ossUrl 优先;否则 `env=prod` 用正式桶,其余(含缺省)用测试桶。
78
+ */
79
+ declare function resolveRuntimeTypesUrl(options?: {
80
+ env?: EasyTwinEnv;
81
+ ossUrl?: string;
82
+ }): string;
83
+ /**
84
+ * 按工作区配置 / 环境变量解析类型 URL。无配置文件时仍可拉缺省测试桶。
85
+ * 只把文件或环境变量里显式给出的 ossUrl 当作覆盖,不把 DEFAULT_OSS_URL 当成显式值。
86
+ */
87
+ declare function resolveRuntimeTypesUrlForCwd(cwd: string, env?: NodeJS.ProcessEnv): Promise<string>;
88
+ /** D29 之前项目根的旧配置路径。 */
89
+ declare function legacyConfigFilePath(cwd: string): string;
90
+ /** `easytwin scene pull` 缺省输出:`.easytwin/scenes/<id>.scene.json`。 */
91
+ declare function defaultSceneOutPath(cwd: string, id: string): string;
63
92
  declare function parseConfig(raw: string): EasyTwinConfig;
64
93
  declare function validateConfigShape(value: unknown): EasyTwinConfig;
65
94
  declare function readConfigFile(cwd: string): Promise<EasyTwinConfig>;
@@ -68,12 +97,15 @@ declare function resolveConfig(file: EasyTwinConfig, env?: NodeJS.ProcessEnv): R
68
97
  declare function loadConfig(cwd: string, env?: NodeJS.ProcessEnv): Promise<ResolvedConfig>;
69
98
  declare function writeConfigFile(cwd: string, config: EasyTwinConfig): Promise<string>;
70
99
  declare const GITIGNORE_FILE_NAME = ".gitignore";
71
- declare const GITIGNORE_ENTRY = "easytwin.config.json";
100
+ /** 凭据文件 gitignore 条目(相对项目根)。 */
101
+ declare const GITIGNORE_ENTRY = ".easytwin/easytwin.config.json";
102
+ /** init 写入 .gitignore 的条目:凭据 + 场景快照目录。不忽略整个 `.easytwin/`,以便 types 可入库。 */
103
+ declare const GITIGNORE_ENTRIES: readonly [".easytwin/easytwin.config.json", ".easytwin/scenes/"];
72
104
  interface GitignoreResult {
73
105
  created: boolean;
74
106
  added: boolean;
75
107
  }
76
- /** 把配置文件名追加进 .gitignore(无则创建),幂等:已存在条目时不重复追加。 */
108
+ /** 把凭据与场景快照路径追加进 .gitignore(无则创建),幂等:已存在条目时不重复追加。 */
77
109
  declare function appendGitignore(cwd: string): Promise<GitignoreResult>;
78
110
  interface InitResult {
79
111
  configFile: string;
@@ -202,6 +234,7 @@ declare function listScenes(client: EasyTwinClient, options?: {
202
234
  }): Promise<SceneSummary[]>;
203
235
  declare function pullScene(client: EasyTwinClient, id: string, options?: {
204
236
  exampleFile?: string;
237
+ cwd?: string;
205
238
  }): Promise<SceneDetail>;
206
239
  declare function saveSceneJson(scene: SceneDetail, out: string): Promise<string>;
207
240
  /** 场景对象树节点:由场景 JSON 的 objs/hierarchy 按 parentObjId 组装(侧边栏场景结构树等复用)。 */
@@ -218,6 +251,37 @@ interface SceneTreeNode {
218
251
  * scene 可为已解析对象或 JSON 字符串。
219
252
  */
220
253
  declare function parseSceneStructure(scene: unknown): SceneTreeNode[];
254
+ /** inspect 默认最多打印的节点数(D30)。 */
255
+ declare const INSPECT_NODE_LIMIT = 200;
256
+ interface InspectSceneFilter {
257
+ name?: string;
258
+ type?: string;
259
+ }
260
+ interface InspectSceneOptions extends InspectSceneFilter {
261
+ cwd: string;
262
+ id: string;
263
+ limit?: number;
264
+ }
265
+ interface InspectSceneResult {
266
+ file: string;
267
+ roots: SceneTreeNode[];
268
+ text: string;
269
+ printed: number;
270
+ total: number;
271
+ truncated: boolean;
272
+ }
273
+ declare function nodeMatchesInspect(node: SceneTreeNode, filter: InspectSceneFilter): boolean;
274
+ /** 无过滤原样返回;有过滤时保留匹配节点与祖先,丢掉未匹配旁支与未匹配子孙。 */
275
+ declare function filterSceneTree(roots: SceneTreeNode[], filter?: InspectSceneFilter): SceneTreeNode[];
276
+ declare function countSceneTreeNodes(roots: SceneTreeNode[]): number;
277
+ declare function formatSceneTree(roots: SceneTreeNode[], limit?: number): {
278
+ text: string;
279
+ printed: number;
280
+ total: number;
281
+ truncated: boolean;
282
+ };
283
+ /** 只读本地 `.easytwin/scenes/<id>.scene.json`,打印对象树。不拉网、不读凭据。 */
284
+ declare function inspectScene(options: InspectSceneOptions): Promise<InspectSceneResult>;
221
285
 
222
286
  interface UploadProgress {
223
287
  phase: "collect" | "upload";
@@ -460,8 +524,15 @@ interface SyncOptions {
460
524
  version?: string;
461
525
  /** skills 源目录;缺省按 devkit 包内 skills/ 解析(CLI)。插件 bundle 场景显式传入插件内目录。 */
462
526
  sourceDir?: string;
463
- /** runtime index.d.ts 源文件;缺省按包内 src/lib 或 dist/runtime-types 解析。插件必须显式传入。 */
527
+ /**
528
+ * 覆盖 runtime 类型源文件(跳过 OSS)。单测 / 离线用;
529
+ * 缺省按配置 env 从官方 OSS 拉 `easytwin/system/libs/runtime/types/index.d.ts`(D31)。
530
+ */
464
531
  typesSourceFile?: string;
532
+ /** 拉取类型用的 fetch;缺省 global fetch。单测注入。 */
533
+ fetch?: typeof fetch;
534
+ /** 解析类型 URL 时覆盖 process.env。 */
535
+ env?: NodeJS.ProcessEnv;
465
536
  }
466
537
  declare const CODEX_MARKER_BEGIN = "<!-- EASYTWIN-SKILLS:BEGIN -->";
467
538
  declare const CODEX_MARKER_END = "<!-- EASYTWIN-SKILLS:END -->";
@@ -473,11 +544,14 @@ declare const TSCONFIG_PATHS_HINT = "\u5DF2\u6709 tsconfig.json,\u672A\u6539\u52
473
544
  declare const APPS_TYPES_FILE = "apps.d.ts";
474
545
  declare const APPS_DTS = "import type { RuntimeEngine, RuntimeScene, SceneManager } from \"@easytwin/runtime\";\n\nexport type TwinAppContext = {\n app: {\n id: string;\n mode: \"preview\" | \"publish\";\n };\n engine: RuntimeEngine;\n container: HTMLElement;\n runtimeScene: RuntimeScene | null;\n scene: RuntimeScene[\"sceneObject\"] | null;\n camera: RuntimeScene[\"camera\"][\"main\"] | null;\n sceneManager: {\n currentSceneId: string;\n runtime: SceneManager | null;\n loadScene(sceneId: string): Promise<void>;\n };\n logger: {\n log(...args: unknown[]): void;\n info(...args: unknown[]): void;\n warn(...args: unknown[]): void;\n error(...args: unknown[]): void;\n };\n assets: {\n text(path: string): Promise<string>;\n json<T = unknown>(path: string): Promise<T>;\n };\n cleanup(fn: () => void | Promise<void>): void;\n sceneCleanup(fn: () => void | Promise<void>): void;\n};\n\nexport declare abstract class TwinApp {\n init?(ctx: TwinAppContext): void | Promise<void>;\n onUpdate?(ctx: TwinAppContext, delta: number, elapsed: number): void;\n onBeforeSceneUnload?(ctx: TwinAppContext): void | Promise<void>;\n onSceneLoaded?(ctx: TwinAppContext): void | Promise<void>;\n onDispose?(ctx: TwinAppContext): void | Promise<void>;\n onError?(ctx: TwinAppContext, error: unknown): boolean | void;\n}\n\nexport declare function defineApp<T>(app: T): T;\n";
475
546
  declare function buildRuntimeTypesContent(sourceDts: string): string;
547
+ declare class SkillsError extends Error {
548
+ constructor(message: string);
549
+ }
476
550
  declare function normalizeTargets(target?: SyncTargetArg | SyncTarget[]): SyncTarget[];
477
551
  /** skills 源目录默认解析:CLI 下为 devkit 包内 skills/。插件 bundle 到 CJS 后 import.meta.url 为空,
478
552
  * 必须通过 SyncOptions.sourceDir / detectSkillsStatus 的 sourceDir 显式传入(见 esbuild.config.mjs 注释)。 */
479
553
  declare function resolveSkillsSourceDir(): string;
480
- /** runtime 类型声明源:开发态 src/lib/index.d.ts,发布态 dist/runtime-types/index.d.ts。 */
554
+ /** 开发态/单测:包内 src/lib/index.d.ts。正式 sync 默认走 OSS,不经过此函数。 */
481
555
  declare function resolveRuntimeTypesSourceFile(): string;
482
556
  declare function readDevkitVersion(skillsDir?: string): Promise<string>;
483
557
  interface TypesSyncSummary {
@@ -680,4 +754,4 @@ declare function buildPreviewHtml(options: {
680
754
  tests?: WorkspaceTestCase[];
681
755
  }): string;
682
756
 
683
- export { APPS_DTS, APPS_MODULE, APPS_TYPES_FILE, APP_ID_HEADER, AUTH_HEADER, BundleError, type BundleResult, type BundleUserCodeOptions, type BundleWorkspaceModuleOptions, CODEX_MARKER_BEGIN, CODEX_MARKER_END, CONFIG_FILE_NAME, type CollectedFile, ConfigError, type ConfigScene, DEFAULT_BASE_URL, DEFAULT_BUNDLE_OUT, DEFAULT_ENTRY_PATH, DEFAULT_MAIN_TS, DEFAULT_OSS_URL, EASYTWIN_TYPES_DIR, ENDPOINTS, EXAMPLE_SCENE_FILE, EasyTwinApiError, EasyTwinClient, type EasyTwinConfig, type EasyTwinEnv, GITIGNORE_ENTRY, GITIGNORE_FILE_NAME, type GitignoreResult, type InitResult, LOCAL_LOAD_SCENE_ERROR, type LinkedScene, META_FILE_NAME, MINIMAL_TSCONFIG, MISSING_TICK_ERROR, MOCK_APP_ID, MOCK_APP_SECRET, MOCK_SCENE_NAME, PORTABLE_RUNTIME_EXPORTS, type PortableRuntimeExport, type PreviewHostKind, RUNTIME_MODULE, type RequestOptions, type ResolvedConfig, SKILL_NAMES, type SceneDetail, type SceneSummary, type SceneTreeNode, type SkillsStatus, type SkillsTargetStatus, type SyncAction, type SyncEntrySummary, type SyncOptions, type SyncSkillsResult, type SyncSummary, type SyncTarget, type SyncTargetArg, TEST_BASE_URL, TSCONFIG_PATHS_HINT, TwinApp, type TwinAppContext, type TwinAppEngine, TwinAppPreviewHost, type TwinAppPreviewHostOptions, type TwinAppRuntimeModule, type TwinAppRuntimeScene, type TwinAppSceneManager, type TwinAppTickListener, type TypesSyncSummary, USER_ENTRY, type UploadCreate, type UploadDelete, type UploadOptions, type UploadProgress, type UploadResult, type UploadUpdate, WORKSPACE_CODE_EXTENSIONS, WORKSPACE_IGNORED_DIRS, WORKSPACE_IGNORED_FILES, type WorkspaceChangeKind, type WorkspaceCodeConfig, type WorkspaceCodeFile, type WorkspaceFileChange, type WorkspacePullApplyResult, type WorkspacePullOptions, type WorkspacePullPlan, type WorkspacePullResult, type WorkspaceTestCase, type WorkspaceUploadPlan, appendGitignore, applyWorkspacePull, assertUploadable, buildMultipartBody, buildPreviewHtml, buildRuntimeTypesContent, buildStatusHtml, buildWorkspacePushBody, bundleUserCode, bundleWorkspaceModule, bundleWorkspaceTestFile, collectFiles, configFilePath, createAppInstance, defaultPullIgnore, defaultWorkspaceIgnore, defineApp, deriveExampleSceneId, detectSkillsStatus, directoryDepth, escapeHtml, escapeJsonForScript, exampleSceneSummary, extractInlineSourceMap, extractSceneArray, formatUploadResult, formatWorkspacePullPlan, formatWorkspacePullResult, initConfig, isIgnoredWorkspacePath, isMockCredentials, isSafeRelPath, isWorkspaceCodeFile, isWorkspaceSpecFile, listScenes, listWorkspaceTests, loadConfig, loadExampleScene, normalizeLinkedScenes, normalizePath, normalizeSceneList, normalizeTargets, normalizeWorkspaceConfig, normalizeWorkspaceFiles, parseConfig, parseExportedTestFunctions, parseSceneStructure, planWorkspacePull, planWorkspaceUpload, portableRuntimeWarning, pullScene, pullWorkspace, readConfigFile, readDevkitVersion, remapErrorStack, resolveConfig, resolveExampleScenePath, resolveRuntimeTypesSourceFile, resolveSkillsSourceDir, resolveSnapshotUrl, resolveWorkspaceAssetPath, saveSceneJson, syncSkills, toConfigScenes, typesMissingHint, uploadDirectory, validateConfigShape, workspacePullHasConflicts, workspacePullPendingWrites, workspaceTestId, writeConfigFile, writeConfigScenes };
757
+ export { APPS_DTS, APPS_MODULE, APPS_TYPES_FILE, APP_ID_HEADER, AUTH_HEADER, BundleError, type BundleResult, type BundleUserCodeOptions, type BundleWorkspaceModuleOptions, CODEX_MARKER_BEGIN, CODEX_MARKER_END, CONFIG_FILE_NAME, type CollectedFile, ConfigError, type ConfigScene, DEFAULT_BASE_URL, DEFAULT_BUNDLE_OUT, DEFAULT_ENTRY_PATH, DEFAULT_MAIN_TS, DEFAULT_OSS_URL, EASYTWIN_DIR, EASYTWIN_SCENES_DIRNAME, EASYTWIN_TYPES_DIR, ENDPOINTS, EXAMPLE_SCENE_FILE, EasyTwinApiError, EasyTwinClient, type EasyTwinConfig, type EasyTwinEnv, GITIGNORE_ENTRIES, GITIGNORE_ENTRY, GITIGNORE_FILE_NAME, type GitignoreResult, INSPECT_NODE_LIMIT, type InitResult, type InspectSceneFilter, type InspectSceneOptions, type InspectSceneResult, LOCAL_LOAD_SCENE_ERROR, type LinkedScene, META_FILE_NAME, MINIMAL_TSCONFIG, MISSING_TICK_ERROR, MOCK_APP_ID, MOCK_APP_SECRET, MOCK_SCENE_NAME, PORTABLE_RUNTIME_EXPORTS, PROD_OSS_URL, type PortableRuntimeExport, type PreviewHostKind, RUNTIME_MODULE, RUNTIME_TYPES_RELPATH, type RequestOptions, type ResolvedConfig, SKILL_NAMES, type SceneDetail, type SceneSummary, type SceneTreeNode, SkillsError, type SkillsStatus, type SkillsTargetStatus, type SyncAction, type SyncEntrySummary, type SyncOptions, type SyncSkillsResult, type SyncSummary, type SyncTarget, type SyncTargetArg, TEST_BASE_URL, TEST_OSS_URL, TSCONFIG_PATHS_HINT, TwinApp, type TwinAppContext, type TwinAppEngine, TwinAppPreviewHost, type TwinAppPreviewHostOptions, type TwinAppRuntimeModule, type TwinAppRuntimeScene, type TwinAppSceneManager, type TwinAppTickListener, type TypesSyncSummary, USER_ENTRY, type UploadCreate, type UploadDelete, type UploadOptions, type UploadProgress, type UploadResult, type UploadUpdate, WORKSPACE_CODE_EXTENSIONS, WORKSPACE_IGNORED_DIRS, WORKSPACE_IGNORED_FILES, type WorkspaceChangeKind, type WorkspaceCodeConfig, type WorkspaceCodeFile, type WorkspaceFileChange, type WorkspacePullApplyResult, type WorkspacePullOptions, type WorkspacePullPlan, type WorkspacePullResult, type WorkspaceTestCase, type WorkspaceUploadPlan, appendGitignore, applyWorkspacePull, assertUploadable, buildMultipartBody, buildPreviewHtml, buildRuntimeTypesContent, buildStatusHtml, buildWorkspacePushBody, bundleUserCode, bundleWorkspaceModule, bundleWorkspaceTestFile, collectFiles, configFilePath, countSceneTreeNodes, createAppInstance, defaultPullIgnore, defaultSceneOutPath, defaultWorkspaceIgnore, defineApp, deriveExampleSceneId, detectSkillsStatus, directoryDepth, escapeHtml, escapeJsonForScript, exampleSceneSummary, extractInlineSourceMap, extractSceneArray, filterSceneTree, formatSceneTree, formatUploadResult, formatWorkspacePullPlan, formatWorkspacePullResult, initConfig, inspectScene, isIgnoredWorkspacePath, isMockCredentials, isSafeRelPath, isWorkspaceCodeFile, isWorkspaceSpecFile, joinOssPath, legacyConfigFilePath, listScenes, listWorkspaceTests, loadConfig, loadExampleScene, nodeMatchesInspect, normalizeLinkedScenes, normalizePath, normalizeSceneList, normalizeTargets, normalizeWorkspaceConfig, normalizeWorkspaceFiles, parseConfig, parseExportedTestFunctions, parseSceneStructure, planWorkspacePull, planWorkspaceUpload, portableRuntimeWarning, pullScene, pullWorkspace, readConfigFile, readDevkitVersion, remapErrorStack, resolveConfig, resolveExampleScenePath, resolveRuntimeTypesSourceFile, resolveRuntimeTypesUrl, resolveRuntimeTypesUrlForCwd, resolveSkillsSourceDir, resolveSnapshotUrl, resolveWorkspaceAssetPath, saveSceneJson, syncSkills, toConfigScenes, typesMissingHint, uploadDirectory, validateConfigShape, workspacePullHasConflicts, workspacePullPendingWrites, workspaceTestId, writeConfigFile, writeConfigScenes };
package/dist/index.js CHANGED
@@ -1,10 +1,15 @@
1
1
  // src/config.ts
2
2
  import { promises as fs } from "fs";
3
3
  import path from "path";
4
+ var EASYTWIN_DIR = ".easytwin";
4
5
  var CONFIG_FILE_NAME = "easytwin.config.json";
6
+ var EASYTWIN_SCENES_DIRNAME = "scenes";
5
7
  var DEFAULT_BASE_URL = "http://saas-twin.k8s.dtstack.cn/";
6
8
  var TEST_BASE_URL = "http://172.16.125.3:10100/";
7
- var DEFAULT_OSS_URL = "https://dt-easyv-test.oss-cn-hangzhou.aliyuncs.com/";
9
+ var TEST_OSS_URL = "https://dt-easyv-test.oss-cn-hangzhou.aliyuncs.com/";
10
+ var PROD_OSS_URL = "https://dt-easyv-prod.oss-cn-hangzhou.aliyuncs.com/";
11
+ var DEFAULT_OSS_URL = TEST_OSS_URL;
12
+ var RUNTIME_TYPES_RELPATH = "easytwin/system/libs/runtime/types/index.d.ts";
8
13
  var MOCK_APP_ID = "test";
9
14
  var MOCK_APP_SECRET = "test";
10
15
  function isMockCredentials(appId, appSecret) {
@@ -17,8 +22,31 @@ var ConfigError = class extends Error {
17
22
  }
18
23
  };
19
24
  function configFilePath(cwd) {
25
+ return path.join(cwd, EASYTWIN_DIR, CONFIG_FILE_NAME);
26
+ }
27
+ function joinOssPath(ossUrl, relPath) {
28
+ return `${ossUrl.replace(/\/+$/, "")}/${relPath.replace(/^\/+/, "")}`;
29
+ }
30
+ function resolveRuntimeTypesUrl(options = {}) {
31
+ const root = options.ossUrl ?? (options.env === "prod" ? PROD_OSS_URL : TEST_OSS_URL);
32
+ return joinOssPath(root, RUNTIME_TYPES_RELPATH);
33
+ }
34
+ async function resolveRuntimeTypesUrlForCwd(cwd, env = process.env) {
35
+ let file;
36
+ try {
37
+ file = await readConfigFile(cwd);
38
+ } catch {
39
+ }
40
+ const easyEnv = env.EASYTWIN_ENV === "test" || env.EASYTWIN_ENV === "prod" ? env.EASYTWIN_ENV : file?.env;
41
+ const ossUrl = env.EASYTWIN_OSS_URL ?? file?.ossUrl;
42
+ return resolveRuntimeTypesUrl({ env: easyEnv, ossUrl });
43
+ }
44
+ function legacyConfigFilePath(cwd) {
20
45
  return path.join(cwd, CONFIG_FILE_NAME);
21
46
  }
47
+ function defaultSceneOutPath(cwd, id) {
48
+ return path.join(cwd, EASYTWIN_DIR, EASYTWIN_SCENES_DIRNAME, `${path.basename(id)}.scene.json`);
49
+ }
22
50
  function parseConfig(raw) {
23
51
  let parsed;
24
52
  try {
@@ -86,15 +114,39 @@ function parseConfigScenes(value) {
86
114
  return scene;
87
115
  });
88
116
  }
89
- async function readConfigFile(cwd) {
90
- const file = configFilePath(cwd);
91
- let raw;
117
+ async function tryReadText(file) {
118
+ try {
119
+ return await fs.readFile(file, "utf8");
120
+ } catch {
121
+ return void 0;
122
+ }
123
+ }
124
+ async function removeLegacyConfigFile(cwd) {
92
125
  try {
93
- raw = await fs.readFile(file, "utf8");
126
+ await fs.unlink(legacyConfigFilePath(cwd));
94
127
  } catch {
95
- throw new ConfigError(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6 ${file},\u8BF7\u5148\u8FD0\u884C \`easytwin init\``);
96
128
  }
97
- return parseConfig(raw);
129
+ }
130
+ async function migrateLegacyConfigIfPresent(cwd) {
131
+ const raw = await tryReadText(legacyConfigFilePath(cwd));
132
+ if (raw === void 0) return void 0;
133
+ const config = parseConfig(raw);
134
+ await writeConfigFile(cwd, config);
135
+ await removeLegacyConfigFile(cwd);
136
+ await appendGitignore(cwd);
137
+ return config;
138
+ }
139
+ async function readConfigFile(cwd) {
140
+ const file = configFilePath(cwd);
141
+ const raw = await tryReadText(file);
142
+ if (raw !== void 0) {
143
+ const config = parseConfig(raw);
144
+ await removeLegacyConfigFile(cwd);
145
+ return config;
146
+ }
147
+ const migrated = await migrateLegacyConfigIfPresent(cwd);
148
+ if (migrated) return migrated;
149
+ throw new ConfigError(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6 ${file},\u8BF7\u5148\u8FD0\u884C \`easytwin init\``);
98
150
  }
99
151
  function resolveConfig(file, env = process.env) {
100
152
  const appId = env.EASYTWIN_APP_ID ?? file.appId;
@@ -118,12 +170,14 @@ async function writeConfigFile(cwd, config) {
118
170
  if (config.baseUrl) body.baseUrl = config.baseUrl;
119
171
  if (config.ossUrl) body.ossUrl = config.ossUrl;
120
172
  if (config.scenes !== void 0) body.scenes = config.scenes;
121
- await fs.mkdir(cwd, { recursive: true });
173
+ await fs.mkdir(path.dirname(file), { recursive: true });
122
174
  await fs.writeFile(file, JSON.stringify(body, null, 2) + "\n", "utf8");
175
+ await removeLegacyConfigFile(cwd);
123
176
  return file;
124
177
  }
125
178
  var GITIGNORE_FILE_NAME = ".gitignore";
126
- var GITIGNORE_ENTRY = "easytwin.config.json";
179
+ var GITIGNORE_ENTRY = ".easytwin/easytwin.config.json";
180
+ var GITIGNORE_ENTRIES = [GITIGNORE_ENTRY, ".easytwin/scenes/"];
127
181
  async function appendGitignore(cwd) {
128
182
  const file = path.join(cwd, GITIGNORE_FILE_NAME);
129
183
  let content = "";
@@ -134,9 +188,10 @@ async function appendGitignore(cwd) {
134
188
  created = true;
135
189
  }
136
190
  const lines = content.split(/\r?\n/);
137
- if (lines.includes(GITIGNORE_ENTRY)) return { created, added: false };
191
+ const missing = GITIGNORE_ENTRIES.filter((entry) => !lines.includes(entry));
192
+ if (missing.length === 0) return { created, added: false };
138
193
  const prefix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
139
- await fs.writeFile(file, content + prefix + GITIGNORE_ENTRY + "\n", "utf8");
194
+ await fs.writeFile(file, content + prefix + missing.join("\n") + "\n", "utf8");
140
195
  return { created, added: true };
141
196
  }
142
197
  async function initConfig(input, cwd) {
@@ -417,25 +472,29 @@ async function listScenes(client, options = {}) {
417
472
  return scenes;
418
473
  }
419
474
  async function pullScene(client, id, options = {}) {
475
+ let scene;
420
476
  if (client.mock) {
421
- const payload2 = await loadExampleScene(options.exampleFile);
422
- const sceneId = deriveExampleSceneId(payload2);
477
+ const payload = await loadExampleScene(options.exampleFile);
478
+ const sceneId = deriveExampleSceneId(payload);
423
479
  if (sceneId !== id) {
424
480
  throw new Error(`\u672C\u5730\u6D4B\u8BD5\u6A21\u5F0F:\u793A\u4F8B\u573A\u666F id \u4E3A ${sceneId},\u6536\u5230 ${id}(\u672C\u5730\u4EC5\u63D0\u4F9B ${EXAMPLE_SCENE_FILE} \u8FD9\u4E00\u4E2A\u573A\u666F)`);
425
481
  }
426
- return { id: sceneId, name: MOCK_SCENE_NAME, payload: payload2 };
427
- }
428
- const data = await fetchLinkedScenes(client);
429
- const scenes = normalizeLinkedScenes(data);
430
- const hit = scenes.find((s) => s.sceneKey === id || s.id === id);
431
- if (!hit) {
432
- const available = scenes.map((s) => s.sceneKey).filter((k) => k.length > 0);
433
- throw new Error(
434
- available.length > 0 ? `\u672A\u627E\u5230\u573A\u666F ${id}(\u53EF\u7528 Scene Key:${available.join(", ")})` : `\u672A\u627E\u5230\u573A\u666F ${id}`
435
- );
482
+ scene = { id: sceneId, name: MOCK_SCENE_NAME, payload };
483
+ } else {
484
+ const data = await fetchLinkedScenes(client);
485
+ const scenes = normalizeLinkedScenes(data);
486
+ const hit = scenes.find((s) => s.sceneKey === id || s.id === id);
487
+ if (!hit) {
488
+ const available = scenes.map((s) => s.sceneKey).filter((k) => k.length > 0);
489
+ throw new Error(
490
+ available.length > 0 ? `\u672A\u627E\u5230\u573A\u666F ${id}(\u53EF\u7528 Scene Key:${available.join(", ")})` : `\u672A\u627E\u5230\u573A\u666F ${id}`
491
+ );
492
+ }
493
+ const payload = await fetchSnapshotJson(client.ossUrl, hit.snapshotUrl);
494
+ scene = { id: hit.sceneKey, name: hit.name, payload };
436
495
  }
437
- const payload = await fetchSnapshotJson(client.ossUrl, hit.snapshotUrl);
438
- return { id: hit.sceneKey, name: hit.name, payload };
496
+ if (options.cwd) await saveSceneJson(scene, defaultSceneOutPath(options.cwd, scene.id));
497
+ return scene;
439
498
  }
440
499
  async function saveSceneJson(scene, out) {
441
500
  await fs2.mkdir(path2.dirname(out), { recursive: true });
@@ -492,6 +551,86 @@ function parseSceneStructure(scene) {
492
551
  };
493
552
  return order.filter((id) => !isChild.has(id)).map((id) => build2(id, /* @__PURE__ */ new Set()));
494
553
  }
554
+ var INSPECT_NODE_LIMIT = 200;
555
+ var INSPECT_INDENT = " ";
556
+ function nodeMatchesInspect(node, filter) {
557
+ if (filter.name !== void 0 && filter.name.length > 0) {
558
+ if (!node.name.toLowerCase().includes(filter.name.toLowerCase())) return false;
559
+ }
560
+ if (filter.type !== void 0 && filter.type.length > 0) {
561
+ if (node.type !== filter.type) return false;
562
+ }
563
+ return true;
564
+ }
565
+ function filterSceneTree(roots, filter = {}) {
566
+ const hasName = filter.name !== void 0 && filter.name.length > 0;
567
+ const hasType = filter.type !== void 0 && filter.type.length > 0;
568
+ if (!hasName && !hasType) return roots;
569
+ const walk = (node) => {
570
+ const children = [];
571
+ for (const child of node.children) {
572
+ const kept = walk(child);
573
+ if (kept) children.push(kept);
574
+ }
575
+ if (nodeMatchesInspect(node, filter) || children.length > 0) {
576
+ return { ...node, children };
577
+ }
578
+ return void 0;
579
+ };
580
+ return roots.flatMap((node) => {
581
+ const kept = walk(node);
582
+ return kept ? [kept] : [];
583
+ });
584
+ }
585
+ function countSceneTreeNodes(roots) {
586
+ let n = 0;
587
+ const walk = (nodes) => {
588
+ for (const node of nodes) {
589
+ n += 1;
590
+ walk(node.children);
591
+ }
592
+ };
593
+ walk(roots);
594
+ return n;
595
+ }
596
+ function formatSceneTree(roots, limit = INSPECT_NODE_LIMIT) {
597
+ const total = countSceneTreeNodes(roots);
598
+ if (total === 0) {
599
+ return { text: "(\u65E0\u5BF9\u8C61\u7ED3\u6784)\n", printed: 0, total: 0, truncated: false };
600
+ }
601
+ const lines = [];
602
+ let printed = 0;
603
+ const walk = (nodes, depth) => {
604
+ for (const node of nodes) {
605
+ if (printed >= limit) return;
606
+ lines.push(`${INSPECT_INDENT.repeat(depth)}${node.id} ${node.name} ${node.type ?? ""}`);
607
+ printed += 1;
608
+ walk(node.children, depth + 1);
609
+ }
610
+ };
611
+ walk(roots, 0);
612
+ const truncated = printed < total;
613
+ if (truncated) lines.push(`\u5176\u4F59 ${total - printed} \u4E2A,\u8BF7\u52A0 --name/--type`);
614
+ return { text: `${lines.join("\n")}
615
+ `, printed, total, truncated };
616
+ }
617
+ async function inspectScene(options) {
618
+ const file = defaultSceneOutPath(options.cwd, options.id);
619
+ let raw;
620
+ try {
621
+ raw = await fs2.readFile(file, "utf8");
622
+ } catch {
623
+ throw new Error(`\u672A\u627E\u5230\u672C\u5730\u573A\u666F\u6587\u4EF6 ${file},\u8BF7\u5148\u8FD0\u884C \`easytwin scene pull ${options.id}\``);
624
+ }
625
+ let payload;
626
+ try {
627
+ payload = JSON.parse(raw);
628
+ } catch {
629
+ throw new Error(`\u573A\u666F\u6587\u4EF6\u4E0D\u662F\u5408\u6CD5 JSON:${file}`);
630
+ }
631
+ const roots = filterSceneTree(parseSceneStructure(payload), { name: options.name, type: options.type });
632
+ return { file, roots, ...formatSceneTree(roots, options.limit ?? INSPECT_NODE_LIMIT) };
633
+ }
495
634
 
496
635
  // src/upload.ts
497
636
  import { promises as fs3 } from "fs";
@@ -794,7 +933,9 @@ function defaultPullIgnore(relPath) {
794
933
  function isSafeRelPath(relPath) {
795
934
  const n = normalizePath(relPath);
796
935
  if (!n) return false;
797
- if (n.toLowerCase() === "easytwin.config.json") return false;
936
+ const lower = n.toLowerCase();
937
+ if (lower === "easytwin.config.json" || lower.endsWith("/easytwin.config.json")) return false;
938
+ if (lower.split("/")[0] === ".easytwin") return false;
798
939
  const base = n.split("/").pop() ?? "";
799
940
  if (/^tsconfig(\..+)?\.json$/i.test(base)) return false;
800
941
  if (path5.isAbsolute(n) || path5.win32.isAbsolute(n.replace(/\//g, "\\"))) return false;
@@ -1493,6 +1634,12 @@ function buildRuntimeTypesContent(sourceDts) {
1493
1634
  return `${sourceDts.replace(/\s+$/, "")}
1494
1635
  `;
1495
1636
  }
1637
+ var SkillsError = class extends Error {
1638
+ constructor(message) {
1639
+ super(message);
1640
+ this.name = "SkillsError";
1641
+ }
1642
+ };
1496
1643
  function normalizeTargets(target = "all") {
1497
1644
  if (target === "all") return ["cursor", "claude", "codex", "qoder"];
1498
1645
  if (Array.isArray(target)) return [...new Set(target)];
@@ -1507,14 +1654,30 @@ function resolveSkillsSourceDir() {
1507
1654
  }
1508
1655
  function resolveRuntimeTypesSourceFile() {
1509
1656
  if (typeof import.meta.url !== "string" || import.meta.url.length === 0) {
1510
- throw new Error("\u65E0\u6CD5\u5B9A\u4F4D runtime \u7C7B\u578B:\u63D2\u4EF6(bundle/CJS)\u573A\u666F\u5FC5\u987B\u663E\u5F0F\u4F20\u5165 typesSourceFile");
1657
+ throw new Error("\u65E0\u6CD5\u5B9A\u4F4D runtime \u7C7B\u578B:\u63D2\u4EF6(bundle/CJS)\u573A\u666F\u8BF7\u8D70 OSS \u62C9\u53D6,\u4E0D\u8981\u89E3\u6790\u5305\u5185 d.ts");
1511
1658
  }
1512
1659
  const here = path8.dirname(fileURLToPath2(import.meta.url));
1513
- const fromDist = path8.join(here, "runtime-types", "index.d.ts");
1514
1660
  const fromSrc = path8.resolve(here, "lib", "index.d.ts");
1515
- if (existsSync(fromDist)) return fromDist;
1516
1661
  if (existsSync(fromSrc)) return fromSrc;
1517
- throw new Error(`\u65E0\u6CD5\u5B9A\u4F4D runtime \u7C7B\u578B\u58F0\u660E,\u5DF2\u5C1D\u8BD5:${fromDist}, ${fromSrc}`);
1662
+ throw new Error(`\u65E0\u6CD5\u5B9A\u4F4D runtime \u7C7B\u578B\u58F0\u660E,\u5DF2\u5C1D\u8BD5:${fromSrc}`);
1663
+ }
1664
+ async function fetchRuntimeTypesDts(url, fetchImpl) {
1665
+ let res;
1666
+ try {
1667
+ res = await fetchImpl(url);
1668
+ } catch (err) {
1669
+ throw new SkillsError(
1670
+ `\u62C9\u53D6 @easytwin/runtime \u7C7B\u578B\u5931\u8D25:${url} (${err instanceof Error ? err.message : String(err)})`
1671
+ );
1672
+ }
1673
+ if (!res.ok) {
1674
+ throw new SkillsError(`\u62C9\u53D6 @easytwin/runtime \u7C7B\u578B\u5931\u8D25:HTTP ${res.status} ${url}`);
1675
+ }
1676
+ const text = await res.text();
1677
+ if (text.trim().length === 0) {
1678
+ throw new SkillsError(`\u62C9\u53D6 @easytwin/runtime \u7C7B\u578B\u5931\u8D25:\u7A7A\u54CD\u5E94 ${url}`);
1679
+ }
1680
+ return text;
1518
1681
  }
1519
1682
  async function readJson(file) {
1520
1683
  return JSON.parse(await fs7.readFile(file, "utf8"));
@@ -1601,8 +1764,8 @@ function buildCodexSegment(version) {
1601
1764
  `EasyTwin \u5F00\u53D1\u6280\u80FD(\u7531 @easytwin/devkit v${version} \u540C\u6B65):`,
1602
1765
  "",
1603
1766
  "- `easytwin-develop`:\u5DE5\u4F5C\u6D41\u603B\u7EB2,\u5F00\u59CB EasyTwin \u5F00\u53D1\u524D\u5FC5\u8BFB\u3002",
1604
- "- `easytwin-bootstrap`:\u521D\u59CB\u5316\u5E94\u7528\u7EA7\u51ED\u8BC1(easytwin.config.json)\u3002",
1605
- "- `easytwin-scene`:\u5217\u51FA\u573A\u666F\u3001\u62C9\u53D6\u573A\u666F JSON\u3002",
1767
+ "- `easytwin-bootstrap`:\u521D\u59CB\u5316\u5E94\u7528\u7EA7\u51ED\u8BC1(`.easytwin/easytwin.config.json`)\u3002",
1768
+ "- `easytwin-scene`:\u5217\u51FA\u573A\u666F\u3001\u62C9\u53D6\u573A\u666F JSON\u3001inspect \u672C\u5730\u5BF9\u8C61\u6811\u3002",
1606
1769
  "- `easytwin-render`:\u7528 twin runtime \u5F00\u53D1\u6E32\u67D3\u529F\u80FD(\u4E3B\u529B)\u3002",
1607
1770
  "- `easytwin-core`:\u5F15\u64CE\u5185\u6838 API(RuntimeEngine/\u57FA\u7C7B/Time/\u76F8\u673A/\u7269\u7406)\u3002",
1608
1771
  "- `easytwin-upload`:\u62C9\u53D6/\u4E0A\u4F20\u5DE5\u4F5C\u533A\u4EE3\u7801(\u8FDC\u7AEF\u4E3A\u7A7A\u5199\u9ED8\u8BA4\u5165\u53E3;\u4E0A\u4F20\u5168\u91CF\u8986\u76D6\u4E0D\u53EF\u9006)\u3002",
@@ -1641,11 +1804,11 @@ async function syncToCodex(sourceRoot, cwd, version) {
1641
1804
  }
1642
1805
  return { target: "codex", entries: [], codex: { name: "AGENTS.md", action } };
1643
1806
  }
1644
- async function syncRuntimeTypes(cwd, typesSourceFile) {
1807
+ async function syncRuntimeTypes(cwd, sourceDts) {
1645
1808
  const destDir = path8.join(cwd, ".easytwin", "types");
1646
1809
  const destFile = path8.join(destDir, "index.d.ts");
1647
1810
  const appsFile = path8.join(destDir, APPS_TYPES_FILE);
1648
- const content = buildRuntimeTypesContent(await fs7.readFile(typesSourceFile, "utf8"));
1811
+ const content = buildRuntimeTypesContent(sourceDts);
1649
1812
  let runtimeCurrent = "";
1650
1813
  let appsCurrent = "";
1651
1814
  let runtimeExists = true;
@@ -1695,7 +1858,11 @@ async function syncSkills(options) {
1695
1858
  else if (target === "qoder") summaries.push(await syncToDir("qoder", sourceRoot, path8.join(options.cwd, ".qoder", "skills"), version));
1696
1859
  else summaries.push(await syncToCodex(sourceRoot, options.cwd, version));
1697
1860
  }
1698
- const types = await syncRuntimeTypes(options.cwd, options.typesSourceFile ?? resolveRuntimeTypesSourceFile());
1861
+ const typesSource = options.typesSourceFile ? await fs7.readFile(options.typesSourceFile, "utf8") : await fetchRuntimeTypesDts(
1862
+ await resolveRuntimeTypesUrlForCwd(options.cwd, options.env ?? process.env),
1863
+ options.fetch ?? fetch
1864
+ );
1865
+ const types = await syncRuntimeTypes(options.cwd, typesSource);
1699
1866
  return { summaries, types };
1700
1867
  }
1701
1868
  async function detectSkillsStatus(cwd, sourceDir) {
@@ -2679,13 +2846,17 @@ export {
2679
2846
  DEFAULT_ENTRY_PATH,
2680
2847
  DEFAULT_MAIN_TS,
2681
2848
  DEFAULT_OSS_URL,
2849
+ EASYTWIN_DIR,
2850
+ EASYTWIN_SCENES_DIRNAME,
2682
2851
  EASYTWIN_TYPES_DIR,
2683
2852
  ENDPOINTS,
2684
2853
  EXAMPLE_SCENE_FILE,
2685
2854
  EasyTwinApiError,
2686
2855
  EasyTwinClient,
2856
+ GITIGNORE_ENTRIES,
2687
2857
  GITIGNORE_ENTRY,
2688
2858
  GITIGNORE_FILE_NAME,
2859
+ INSPECT_NODE_LIMIT,
2689
2860
  LOCAL_LOAD_SCENE_ERROR,
2690
2861
  META_FILE_NAME,
2691
2862
  MINIMAL_TSCONFIG,
@@ -2694,9 +2865,13 @@ export {
2694
2865
  MOCK_APP_SECRET,
2695
2866
  MOCK_SCENE_NAME,
2696
2867
  PORTABLE_RUNTIME_EXPORTS,
2868
+ PROD_OSS_URL,
2697
2869
  RUNTIME_MODULE,
2870
+ RUNTIME_TYPES_RELPATH,
2698
2871
  SKILL_NAMES,
2872
+ SkillsError,
2699
2873
  TEST_BASE_URL,
2874
+ TEST_OSS_URL,
2700
2875
  TSCONFIG_PATHS_HINT,
2701
2876
  TwinApp,
2702
2877
  TwinAppPreviewHost,
@@ -2717,8 +2892,10 @@ export {
2717
2892
  bundleWorkspaceTestFile,
2718
2893
  collectFiles,
2719
2894
  configFilePath,
2895
+ countSceneTreeNodes,
2720
2896
  createAppInstance,
2721
2897
  defaultPullIgnore,
2898
+ defaultSceneOutPath,
2722
2899
  defaultWorkspaceIgnore,
2723
2900
  defineApp,
2724
2901
  deriveExampleSceneId,
@@ -2729,19 +2906,25 @@ export {
2729
2906
  exampleSceneSummary,
2730
2907
  extractInlineSourceMap,
2731
2908
  extractSceneArray,
2909
+ filterSceneTree,
2910
+ formatSceneTree,
2732
2911
  formatUploadResult,
2733
2912
  formatWorkspacePullPlan,
2734
2913
  formatWorkspacePullResult,
2735
2914
  initConfig,
2915
+ inspectScene,
2736
2916
  isIgnoredWorkspacePath,
2737
2917
  isMockCredentials,
2738
2918
  isSafeRelPath,
2739
2919
  isWorkspaceCodeFile,
2740
2920
  isWorkspaceSpecFile,
2921
+ joinOssPath,
2922
+ legacyConfigFilePath,
2741
2923
  listScenes,
2742
2924
  listWorkspaceTests,
2743
2925
  loadConfig,
2744
2926
  loadExampleScene,
2927
+ nodeMatchesInspect,
2745
2928
  normalizeLinkedScenes,
2746
2929
  normalizePath,
2747
2930
  normalizeSceneList,
@@ -2762,6 +2945,8 @@ export {
2762
2945
  resolveConfig,
2763
2946
  resolveExampleScenePath,
2764
2947
  resolveRuntimeTypesSourceFile,
2948
+ resolveRuntimeTypesUrl,
2949
+ resolveRuntimeTypesUrlForCwd,
2765
2950
  resolveSkillsSourceDir,
2766
2951
  resolveSnapshotUrl,
2767
2952
  resolveWorkspaceAssetPath,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@easytwin/devkit",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "EasyTwin DevKit 核心:lib(函数库)+ bin(easytwin 命令)双导出",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -55,7 +55,7 @@
55
55
  "registry": "https://registry.npmjs.org/"
56
56
  },
57
57
  "scripts": {
58
- "build": "tsup && node scripts/copy-runtime-types.mjs && node scripts/bundle-runtime.mjs",
58
+ "build": "tsup && node scripts/bundle-runtime.mjs",
59
59
  "test": "vitest run",
60
60
  "typecheck": "tsc --noEmit"
61
61
  }