@easytwin/devkit 0.1.0 → 0.1.1

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.js CHANGED
@@ -2,7 +2,14 @@
2
2
  import { promises as fs } from "fs";
3
3
  import path from "path";
4
4
  var CONFIG_FILE_NAME = "easytwin.config.json";
5
- var DEFAULT_BASE_URL = "https://api.easytwin.example.com";
5
+ var DEFAULT_BASE_URL = "http://saas-twin.k8s.dtstack.cn/";
6
+ var TEST_BASE_URL = "http://saas-twin-test.k8s.dtstack.cn/";
7
+ var DEFAULT_OSS_URL = "https://dt-easyv-test.oss-cn-hangzhou.aliyuncs.com/";
8
+ var MOCK_APP_ID = "test";
9
+ var MOCK_APP_SECRET = "test";
10
+ function isMockCredentials(appId, appSecret) {
11
+ return appId === MOCK_APP_ID && appSecret === MOCK_APP_SECRET;
12
+ }
6
13
  var ConfigError = class extends Error {
7
14
  constructor(message) {
8
15
  super(message);
@@ -35,8 +42,16 @@ function validateConfigShape(value) {
35
42
  if (v.baseUrl !== void 0 && (typeof v.baseUrl !== "string" || v.baseUrl.length === 0)) {
36
43
  throw new ConfigError("baseUrl \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
37
44
  }
45
+ if (v.ossUrl !== void 0 && (typeof v.ossUrl !== "string" || v.ossUrl.length === 0)) {
46
+ throw new ConfigError("ossUrl \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
47
+ }
48
+ if (v.env !== void 0 && v.env !== "prod" && v.env !== "test") {
49
+ throw new ConfigError("env \u5FC5\u987B\u662F prod \u6216 test");
50
+ }
38
51
  const config = { appId: v.appId, appSecret: v.appSecret };
52
+ if (v.env === "prod" || v.env === "test") config.env = v.env;
39
53
  if (typeof v.baseUrl === "string") config.baseUrl = v.baseUrl;
54
+ if (typeof v.ossUrl === "string") config.ossUrl = v.ossUrl;
40
55
  return config;
41
56
  }
42
57
  async function readConfigFile(cwd) {
@@ -52,11 +67,13 @@ async function readConfigFile(cwd) {
52
67
  function resolveConfig(file, env = process.env) {
53
68
  const appId = env.EASYTWIN_APP_ID ?? file.appId;
54
69
  const appSecret = env.EASYTWIN_APP_SECRET ?? file.appSecret;
55
- const baseUrl = env.EASYTWIN_BASE_URL ?? file.baseUrl ?? DEFAULT_BASE_URL;
70
+ const easyEnv = env.EASYTWIN_ENV === "test" || env.EASYTWIN_ENV === "prod" ? env.EASYTWIN_ENV : file.env ?? "prod";
71
+ const baseUrl = env.EASYTWIN_BASE_URL ?? file.baseUrl ?? (easyEnv === "test" ? TEST_BASE_URL : DEFAULT_BASE_URL);
72
+ const ossUrl = env.EASYTWIN_OSS_URL ?? file.ossUrl ?? DEFAULT_OSS_URL;
56
73
  if (!appId) throw new ConfigError("\u7F3A\u5C11 appId:\u914D\u7F6E\u6587\u4EF6\u4E2D\u672A\u63D0\u4F9B,\u4E14\u672A\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF EASYTWIN_APP_ID");
57
74
  if (!appSecret) throw new ConfigError("\u7F3A\u5C11 appSecret:\u914D\u7F6E\u6587\u4EF6\u4E2D\u672A\u63D0\u4F9B,\u4E14\u672A\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF EASYTWIN_APP_SECRET");
58
75
  const source = env.EASYTWIN_APP_ID !== void 0 || env.EASYTWIN_APP_SECRET !== void 0 ? "env" : "file";
59
- return { appId, appSecret, baseUrl, source };
76
+ return { appId, appSecret, baseUrl, ossUrl, mock: isMockCredentials(appId, appSecret), source };
60
77
  }
61
78
  async function loadConfig(cwd, env = process.env) {
62
79
  return resolveConfig(await readConfigFile(cwd), env);
@@ -64,7 +81,9 @@ async function loadConfig(cwd, env = process.env) {
64
81
  async function writeConfigFile(cwd, config) {
65
82
  const file = configFilePath(cwd);
66
83
  const body = { appId: config.appId, appSecret: config.appSecret };
84
+ if (config.env) body.env = config.env;
67
85
  if (config.baseUrl) body.baseUrl = config.baseUrl;
86
+ if (config.ossUrl) body.ossUrl = config.ossUrl;
68
87
  await fs.mkdir(cwd, { recursive: true });
69
88
  await fs.writeFile(file, JSON.stringify(body, null, 2) + "\n", "utf8");
70
89
  return file;
@@ -133,10 +152,13 @@ function parseResponseBody(buffer) {
133
152
  }
134
153
  var EasyTwinClient = class {
135
154
  baseUrl;
155
+ /** 本地测试模式:true 时 scene/upload 走本地 mock,不发网络请求(见 config.ts)。 */
156
+ mock;
136
157
  appId;
137
158
  appSecret;
138
159
  constructor(config) {
139
160
  this.baseUrl = config.baseUrl.replace(/\/+$/, "");
161
+ this.mock = config.mock;
140
162
  this.appId = config.appId;
141
163
  this.appSecret = config.appSecret;
142
164
  }
@@ -148,8 +170,8 @@ var EasyTwinClient = class {
148
170
  };
149
171
  }
150
172
  /** JSON 请求(原生 fetch)。 */
151
- async request(path5, options = {}) {
152
- const url = `${this.baseUrl}${path5}`;
173
+ async request(path6, options = {}) {
174
+ const url = `${this.baseUrl}${path6}`;
153
175
  const headers = { ...this.authHeaders(), ...options.headers };
154
176
  if (options.body !== void 0 && options.body !== null && !("Content-Type" in headers)) {
155
177
  headers["Content-Type"] = "application/json";
@@ -171,8 +193,8 @@ var EasyTwinClient = class {
171
193
  * multipart 流式上传(手写 http 请求以支持逐字节进度回调)。
172
194
  * 这是 fetch 上传的替代路径:原生 fetch 无法上报上传进度。
173
195
  */
174
- async upload(path5, options) {
175
- const url = new URL(`${this.baseUrl}${path5}`);
196
+ async upload(path6, options) {
197
+ const url = new URL(`${this.baseUrl}${path6}`);
176
198
  const mod = url.protocol === "https:" ? https : http;
177
199
  const headers = {
178
200
  ...this.authHeaders(),
@@ -214,6 +236,7 @@ var EasyTwinClient = class {
214
236
  // src/scene.ts
215
237
  import { promises as fs2 } from "fs";
216
238
  import path2 from "path";
239
+ import { fileURLToPath } from "url";
217
240
  function normalizeSceneList(data) {
218
241
  const list = Array.isArray(data) ? data : data?.list;
219
242
  if (!Array.isArray(list)) throw new Error("scene list \u54CD\u5E94\u7ED3\u6784\u65E0\u6CD5\u8BC6\u522B");
@@ -226,11 +249,50 @@ function normalizeSceneDetail(data) {
226
249
  const it = data ?? {};
227
250
  return { id: String(it.id ?? ""), name: String(it.name ?? ""), payload: data };
228
251
  }
229
- async function listScenes(client) {
252
+ var EXAMPLE_SCENE_FILE = "scene.example.json";
253
+ var MOCK_SCENE_NAME = "\u672C\u5730\u793A\u4F8B\u573A\u666F";
254
+ function resolveExampleScenePath() {
255
+ if (typeof import.meta.url !== "string" || import.meta.url.length === 0) {
256
+ throw new Error(`\u65E0\u6CD5\u5B9A\u4F4D ${EXAMPLE_SCENE_FILE}:\u63D2\u4EF6(bundle/CJS)\u573A\u666F\u5FC5\u987B\u663E\u5F0F\u4F20\u5165 exampleFile`);
257
+ }
258
+ return path2.resolve(path2.dirname(fileURLToPath(import.meta.url)), "..", EXAMPLE_SCENE_FILE);
259
+ }
260
+ async function loadExampleScene(exampleFile) {
261
+ const file = exampleFile ?? resolveExampleScenePath();
262
+ let raw;
263
+ try {
264
+ raw = await fs2.readFile(file, "utf8");
265
+ } catch {
266
+ throw new Error(`\u65E0\u6CD5\u8BFB\u53D6\u672C\u5730\u793A\u4F8B\u573A\u666F ${file}(\u672C\u5730\u6D4B\u8BD5\u6A21\u5F0F\u9700\u8981 devkit \u5305\u5185\u7684 ${EXAMPLE_SCENE_FILE})`);
267
+ }
268
+ try {
269
+ return JSON.parse(raw);
270
+ } catch {
271
+ throw new Error(`\u672C\u5730\u793A\u4F8B\u573A\u666F\u4E0D\u662F\u5408\u6CD5 JSON:${file}`);
272
+ }
273
+ }
274
+ function deriveExampleSceneId(payload) {
275
+ const root = payload ?? {};
276
+ const id = root?.objs?.map((o) => o.sceneId).find((s) => typeof s === "string" && s.length > 0);
277
+ return id ?? "local";
278
+ }
279
+ async function exampleSceneSummary(exampleFile) {
280
+ return { id: deriveExampleSceneId(await loadExampleScene(exampleFile)), name: MOCK_SCENE_NAME };
281
+ }
282
+ async function listScenes(client, options = {}) {
283
+ if (client.mock) return [await exampleSceneSummary(options.exampleFile)];
230
284
  const data = await client.request(ENDPOINTS.scenes, { method: "GET" });
231
285
  return normalizeSceneList(data);
232
286
  }
233
- async function pullScene(client, id) {
287
+ async function pullScene(client, id, options = {}) {
288
+ if (client.mock) {
289
+ const payload = await loadExampleScene(options.exampleFile);
290
+ const sceneId = deriveExampleSceneId(payload);
291
+ if (sceneId !== id) {
292
+ 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)`);
293
+ }
294
+ return { id: sceneId, name: MOCK_SCENE_NAME, payload };
295
+ }
234
296
  const data = await client.request(ENDPOINTS.scene(id), { method: "GET" });
235
297
  return normalizeSceneDetail(data);
236
298
  }
@@ -239,6 +301,56 @@ async function saveSceneJson(scene, out) {
239
301
  await fs2.writeFile(out, JSON.stringify(scene.payload ?? scene, null, 2) + "\n", "utf8");
240
302
  return out;
241
303
  }
304
+ function parseSceneStructure(scene) {
305
+ let data = scene;
306
+ if (typeof data === "string") {
307
+ try {
308
+ data = JSON.parse(data);
309
+ } catch {
310
+ return [];
311
+ }
312
+ }
313
+ const root = data ?? {};
314
+ const objs = Array.isArray(root.objs) ? root.objs : [];
315
+ const hierarchy = Array.isArray(root.hierarchy) ? root.hierarchy : [];
316
+ const items = objs.length > 0 ? objs : hierarchy;
317
+ const typeById = /* @__PURE__ */ new Map();
318
+ for (const o of objs) {
319
+ if (typeof o.id === "string" && typeof o.type === "string") typeById.set(o.id, o.type);
320
+ }
321
+ const nodes = /* @__PURE__ */ new Map();
322
+ const order = [];
323
+ for (const item of items) {
324
+ const id = typeof item.id === "string" ? item.id : "";
325
+ if (id.length === 0 || nodes.has(id)) continue;
326
+ const name = typeof item.name === "string" && item.name.length > 0 ? item.name : id;
327
+ nodes.set(id, { id, name, type: typeById.get(id), children: [] });
328
+ order.push(id);
329
+ }
330
+ const childrenOf = /* @__PURE__ */ new Map();
331
+ for (const item of items) {
332
+ const id = typeof item.id === "string" ? item.id : "";
333
+ if (id.length === 0 || !nodes.has(id)) continue;
334
+ const parentId = typeof item.parentObjId === "string" && item.parentObjId.length > 0 ? item.parentObjId : "";
335
+ if (parentId.length === 0 || parentId === id || !nodes.has(parentId)) continue;
336
+ const kids = childrenOf.get(parentId) ?? [];
337
+ kids.push(id);
338
+ childrenOf.set(parentId, kids);
339
+ }
340
+ const isChild = /* @__PURE__ */ new Set();
341
+ for (const kids of childrenOf.values()) for (const k of kids) isChild.add(k);
342
+ const build2 = (id, seen) => {
343
+ const node = nodes.get(id);
344
+ const children = [];
345
+ if (!seen.has(id)) {
346
+ seen.add(id);
347
+ for (const kid of childrenOf.get(id) ?? []) children.push(build2(kid, seen));
348
+ seen.delete(id);
349
+ }
350
+ return { ...node, children };
351
+ };
352
+ return order.filter((id) => !isChild.has(id)).map((id) => build2(id, /* @__PURE__ */ new Set()));
353
+ }
242
354
 
243
355
  // src/upload.ts
244
356
  import { promises as fs3 } from "fs";
@@ -292,7 +404,16 @@ Content-Type: application/octet-stream\r
292
404
  `, "utf8"));
293
405
  return Buffer.concat(parts);
294
406
  }
407
+ async function mockUpload(dir, options) {
408
+ const ignore = options.ignore ?? (() => false);
409
+ const files = await collectFiles(dir, ignore);
410
+ const total = files.reduce((sum, f) => sum + f.size, 0);
411
+ options.onProgress?.({ phase: "collect", current: total, total });
412
+ options.onProgress?.({ phase: "upload", current: total, total });
413
+ return { fileCount: files.length, byteCount: total, mock: true };
414
+ }
295
415
  async function uploadDirectory(client, dir, options = {}) {
416
+ if (client.mock) return mockUpload(dir, options);
296
417
  const ignore = options.ignore ?? (() => false);
297
418
  const files = await collectFiles(dir, ignore);
298
419
  const total = files.reduce((sum, f) => sum + f.size, 0);
@@ -315,9 +436,9 @@ async function uploadDirectory(client, dir, options = {}) {
315
436
  }
316
437
 
317
438
  // src/skills.ts
318
- import { promises as fs4 } from "fs";
439
+ import { existsSync, promises as fs4 } from "fs";
319
440
  import path4 from "path";
320
- import { fileURLToPath } from "url";
441
+ import { fileURLToPath as fileURLToPath2 } from "url";
321
442
  var SKILL_NAMES = [
322
443
  "easytwin-render",
323
444
  "easytwin-core",
@@ -329,8 +450,48 @@ var SKILL_NAMES = [
329
450
  var CODEX_MARKER_BEGIN = "<!-- EASYTWIN-SKILLS:BEGIN -->";
330
451
  var CODEX_MARKER_END = "<!-- EASYTWIN-SKILLS:END -->";
331
452
  var META_FILE_NAME = ".easytwin-meta.json";
453
+ var EASYTWIN_TYPES_DIR = ".easytwin/types";
454
+ var MINIMAL_TSCONFIG = `${JSON.stringify(
455
+ {
456
+ compilerOptions: {
457
+ target: "ES2022",
458
+ module: "ESNext",
459
+ moduleResolution: "bundler",
460
+ strict: true,
461
+ skipLibCheck: true,
462
+ noEmit: true,
463
+ paths: {
464
+ "@easytwin/runtime": [".easytwin/types"]
465
+ }
466
+ },
467
+ include: ["src/**/*.ts"]
468
+ },
469
+ null,
470
+ 2
471
+ )}
472
+ `;
473
+ var TSCONFIG_PATHS_HINT = `\u5DF2\u6709 tsconfig.json,\u672A\u6539\u52A8\u3002\u8BF7\u5728 compilerOptions \u4E2D\u52A0\u5165:
474
+ "skipLibCheck": true,
475
+ "paths": {
476
+ "@easytwin/runtime": [".easytwin/types"]
477
+ }`;
478
+ var RUN_CONTEXT_DECL = `
479
+ /** \u9884\u89C8\u9875 Run \u6309\u94AE\u6CE8\u5165\u7684\u8FD0\u884C\u4E0A\u4E0B\u6587(\u89C1 D12)\u3002 */
480
+ export interface EasyTwinRunContext {
481
+ engine: RuntimeEngine;
482
+ runtime: typeof import("@easytwin/runtime");
483
+ sceneJson: SceneJson;
484
+ }
485
+ `;
486
+ function buildRuntimeTypesContent(sourceDts) {
487
+ const trimmed = sourceDts.replace(/\s+$/, "");
488
+ if (trimmed.includes("export interface EasyTwinRunContext")) return `${trimmed}
489
+ `;
490
+ return `${trimmed}
491
+ ${RUN_CONTEXT_DECL}`;
492
+ }
332
493
  function normalizeTargets(target = "all") {
333
- if (target === "all") return ["cursor", "claude", "codex"];
494
+ if (target === "all") return ["cursor", "claude", "codex", "qoder"];
334
495
  if (Array.isArray(target)) return [...new Set(target)];
335
496
  return [target];
336
497
  }
@@ -338,9 +499,20 @@ function resolveSkillsSourceDir() {
338
499
  if (typeof import.meta.url !== "string" || import.meta.url.length === 0) {
339
500
  throw new Error("\u65E0\u6CD5\u5B9A\u4F4D skills \u6E90\u76EE\u5F55:\u63D2\u4EF6(bundle/CJS)\u573A\u666F\u5FC5\u987B\u663E\u5F0F\u4F20\u5165 sourceDir");
340
501
  }
341
- const here = path4.dirname(fileURLToPath(import.meta.url));
502
+ const here = path4.dirname(fileURLToPath2(import.meta.url));
342
503
  return path4.resolve(here, "..", "skills");
343
504
  }
505
+ function resolveRuntimeTypesSourceFile() {
506
+ if (typeof import.meta.url !== "string" || import.meta.url.length === 0) {
507
+ throw new Error("\u65E0\u6CD5\u5B9A\u4F4D runtime \u7C7B\u578B:\u63D2\u4EF6(bundle/CJS)\u573A\u666F\u5FC5\u987B\u663E\u5F0F\u4F20\u5165 typesSourceFile");
508
+ }
509
+ const here = path4.dirname(fileURLToPath2(import.meta.url));
510
+ const fromDist = path4.join(here, "runtime-types", "index.d.ts");
511
+ const fromSrc = path4.resolve(here, "lib", "index.d.ts");
512
+ if (existsSync(fromDist)) return fromDist;
513
+ if (existsSync(fromSrc)) return fromSrc;
514
+ throw new Error(`\u65E0\u6CD5\u5B9A\u4F4D runtime \u7C7B\u578B\u58F0\u660E,\u5DF2\u5C1D\u8BD5:${fromDist}, ${fromSrc}`);
515
+ }
344
516
  async function readJson(file) {
345
517
  return JSON.parse(await fs4.readFile(file, "utf8"));
346
518
  }
@@ -465,6 +637,38 @@ async function syncToCodex(sourceRoot, cwd, version) {
465
637
  }
466
638
  return { target: "codex", entries: [], codex: { name: "AGENTS.md", action } };
467
639
  }
640
+ async function syncRuntimeTypes(cwd, typesSourceFile) {
641
+ const destDir = path4.join(cwd, ".easytwin", "types");
642
+ const destFile = path4.join(destDir, "index.d.ts");
643
+ const content = buildRuntimeTypesContent(await fs4.readFile(typesSourceFile, "utf8"));
644
+ let exists = true;
645
+ let current = "";
646
+ try {
647
+ current = await fs4.readFile(destFile, "utf8");
648
+ } catch {
649
+ exists = false;
650
+ }
651
+ const action = actionFor(exists, current === content);
652
+ if (action !== "unchanged") {
653
+ await fs4.mkdir(destDir, { recursive: true });
654
+ await fs4.writeFile(destFile, content, "utf8");
655
+ }
656
+ const tsconfigPath = path4.join(cwd, "tsconfig.json");
657
+ let tsconfig;
658
+ let pathsHint;
659
+ try {
660
+ const existing = await fs4.readFile(tsconfigPath, "utf8");
661
+ if (existing === MINIMAL_TSCONFIG) tsconfig = "unchanged";
662
+ else {
663
+ tsconfig = "manual-paths";
664
+ pathsHint = TSCONFIG_PATHS_HINT;
665
+ }
666
+ } catch {
667
+ await fs4.writeFile(tsconfigPath, MINIMAL_TSCONFIG, "utf8");
668
+ tsconfig = "created";
669
+ }
670
+ return { action, tsconfig, pathsHint };
671
+ }
468
672
  async function syncSkills(options) {
469
673
  const targets = normalizeTargets(options.targets ?? "all");
470
674
  const sourceRoot = options.sourceDir ?? resolveSkillsSourceDir();
@@ -473,16 +677,24 @@ async function syncSkills(options) {
473
677
  for (const target of targets) {
474
678
  if (target === "cursor") summaries.push(await syncToDir("cursor", sourceRoot, path4.join(options.cwd, ".cursor", "skills"), version));
475
679
  else if (target === "claude") summaries.push(await syncToDir("claude", sourceRoot, path4.join(options.cwd, ".claude", "skills"), version));
680
+ else if (target === "qoder") summaries.push(await syncToDir("qoder", sourceRoot, path4.join(options.cwd, ".qoder", "skills"), version));
476
681
  else summaries.push(await syncToCodex(sourceRoot, options.cwd, version));
477
682
  }
478
- return summaries;
683
+ const types = await syncRuntimeTypes(options.cwd, options.typesSourceFile ?? resolveRuntimeTypesSourceFile());
684
+ return { summaries, types };
479
685
  }
480
686
  async function detectSkillsStatus(cwd, sourceDir) {
481
687
  const sourceRoot = sourceDir ?? resolveSkillsSourceDir();
482
688
  const version = await readDevkitVersion(sourceRoot);
483
689
  const targets = [];
484
- for (const target of ["cursor", "claude"]) {
485
- const root = path4.join(cwd, target === "cursor" ? ".cursor/skills" : ".claude/skills");
690
+ const dirTargets = ["cursor", "claude", "qoder"];
691
+ const DIR_ROOTS = {
692
+ cursor: ".cursor/skills",
693
+ claude: ".claude/skills",
694
+ qoder: ".qoder/skills"
695
+ };
696
+ for (const target of dirTargets) {
697
+ const root = path4.join(cwd, DIR_ROOTS[target]);
486
698
  const missing = [];
487
699
  for (const name of SKILL_NAMES) {
488
700
  if (!await fs4.stat(path4.join(root, name)).then(() => true).catch(() => false)) missing.push(name);
@@ -507,43 +719,258 @@ async function detectSkillsStatus(cwd, sourceDir) {
507
719
  }
508
720
  const hasCodex = agentsContent.includes(CODEX_MARKER_BEGIN) && agentsContent.includes(CODEX_MARKER_END) && agentsContent.includes(`v${version}`);
509
721
  targets.push({ target: "codex", synced: hasCodex, reason: hasCodex ? void 0 : "AGENTS.md \u7F3A\u5C11\u540C\u6B65\u6807\u8BB0\u6BB5" });
510
- return { cwd, targets };
722
+ let typesSynced = false;
723
+ try {
724
+ const dts = await fs4.readFile(path4.join(cwd, ".easytwin", "types", "index.d.ts"), "utf8");
725
+ typesSynced = dts.includes("export interface EasyTwinRunContext");
726
+ } catch {
727
+ typesSynced = false;
728
+ }
729
+ return { cwd, targets, typesSynced };
730
+ }
731
+
732
+ // src/bundle.ts
733
+ import * as esbuild from "esbuild-wasm";
734
+ import { promises as fs5 } from "fs";
735
+ import path5 from "path";
736
+ var USER_ENTRY = "src/main.ts";
737
+ var DEFAULT_BUNDLE_OUT = "dist/main.js";
738
+ var RUNTIME_MODULE = "@easytwin/runtime";
739
+ var BundleError = class extends Error {
740
+ constructor(message) {
741
+ super(message);
742
+ this.name = "BundleError";
743
+ }
744
+ };
745
+ function isRelativeOrAbsolute(spec) {
746
+ return spec.startsWith("./") || spec.startsWith("../") || spec.startsWith("/") || path5.isAbsolute(spec);
747
+ }
748
+ function whitelistPlugin() {
749
+ return {
750
+ name: "easytwin-whitelist",
751
+ setup(build2) {
752
+ build2.onResolve({ filter: /.*/ }, (args) => {
753
+ if (args.kind === "entry-point") return void 0;
754
+ if (isRelativeOrAbsolute(args.path)) return void 0;
755
+ if (args.path === RUNTIME_MODULE) return { path: args.path, external: true };
756
+ return {
757
+ errors: [
758
+ {
759
+ text: `\u68C0\u6D4B\u5230\u5916\u90E8\u4F9D\u8D56 ${args.path},\u7528\u6237\u4EE3\u7801\u53EA\u80FD\u4F9D\u8D56 @easytwin/runtime\u3002\u4E0D\u8981 npm install,\u7C7B\u578B\u7531 easytwin skills sync \u5206\u53D1,\u8FD0\u884C\u8D70\u9884\u89C8\u9875 Run \u6216 easytwin bundle\u3002`
760
+ }
761
+ ]
762
+ };
763
+ });
764
+ }
765
+ };
766
+ }
767
+ function formatEsbuildMessages(messages) {
768
+ return messages.map((m) => {
769
+ const loc = m.location ? `${m.location.file}:${m.location.line}:${m.location.column}: ` : "";
770
+ return `${loc}${m.text}`;
771
+ }).join("\n");
772
+ }
773
+ async function bundleUserCode(options) {
774
+ const cwd = path5.resolve(options.cwd);
775
+ const entry = path5.join(cwd, USER_ENTRY);
776
+ try {
777
+ await fs5.access(entry);
778
+ } catch {
779
+ throw new BundleError(
780
+ `\u672A\u627E\u5230\u5165\u53E3\u6587\u4EF6 ${entry}\u3002\u8BF7\u5728\u5DE5\u4F5C\u533A\u521B\u5EFA src/main.ts,\u5E76 \`export default async function main(ctx)\`\u3002`
781
+ );
782
+ }
783
+ let result;
784
+ try {
785
+ result = await esbuild.build({
786
+ absWorkingDir: cwd,
787
+ entryPoints: [entry],
788
+ bundle: true,
789
+ write: false,
790
+ format: "esm",
791
+ platform: "browser",
792
+ target: "es2022",
793
+ sourcemap: "inline",
794
+ logLevel: "silent",
795
+ plugins: [whitelistPlugin()]
796
+ });
797
+ } catch (err) {
798
+ const errors = err.errors;
799
+ if (Array.isArray(errors) && errors.length > 0) {
800
+ throw new BundleError(formatEsbuildMessages(errors));
801
+ }
802
+ throw err instanceof Error ? new BundleError(err.message) : err;
803
+ }
804
+ if (result.errors.length > 0) {
805
+ throw new BundleError(formatEsbuildMessages(result.errors));
806
+ }
807
+ const file = result.outputFiles?.[0];
808
+ if (!file) throw new BundleError("\u6253\u5305\u672A\u4EA7\u51FA\u6587\u4EF6");
809
+ const code = file.text;
810
+ const warnings = result.warnings.map((w) => formatEsbuildMessages([w]));
811
+ if (options.outFile) {
812
+ const outFile = path5.isAbsolute(options.outFile) ? options.outFile : path5.join(cwd, options.outFile);
813
+ await fs5.mkdir(path5.dirname(outFile), { recursive: true });
814
+ await fs5.writeFile(outFile, code, "utf8");
815
+ }
816
+ return { code, warnings };
817
+ }
818
+ async function typesMissingHint(cwd) {
819
+ try {
820
+ await fs5.access(path5.join(cwd, ".easytwin", "types", "index.d.ts"));
821
+ return void 0;
822
+ } catch {
823
+ return "\u672A\u627E\u5230 .easytwin/types,\u8FD0\u884C `easytwin skills sync` \u4EE5\u5206\u53D1 @easytwin/runtime \u7C7B\u578B\u58F0\u660E\u3002";
824
+ }
825
+ }
826
+ var SOURCEMAP_RE = /sourceMappingURL=data:application\/json(?:;charset=[^;]+)?;base64,([A-Za-z0-9+/]+=*)/;
827
+ var VLQ_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
828
+ function decodeVLQValues(str) {
829
+ const values = [];
830
+ let i = 0;
831
+ while (i < str.length) {
832
+ let result = 0;
833
+ let shift = 0;
834
+ let continuation = true;
835
+ while (continuation) {
836
+ if (i >= str.length) return values;
837
+ const digit = VLQ_CHARS.indexOf(str[i++] ?? "");
838
+ if (digit < 0) return values;
839
+ continuation = (digit & 32) !== 0;
840
+ result += (digit & 31) << shift;
841
+ shift += 5;
842
+ }
843
+ values.push(result & 1 ? -(result >> 1) : result >> 1);
844
+ }
845
+ return values;
846
+ }
847
+ function decodeMappings(map) {
848
+ const sources = map.sources ?? [];
849
+ const lines = (map.mappings ?? "").split(";");
850
+ let sourceIndex = 0;
851
+ let originalLine = 0;
852
+ let originalColumn = 0;
853
+ const decoded = [];
854
+ for (const line of lines) {
855
+ let generatedColumn = 0;
856
+ const segs = [];
857
+ if (line) {
858
+ for (const raw of line.split(",")) {
859
+ if (!raw) continue;
860
+ const nums = decodeVLQValues(raw);
861
+ if (nums[0] === void 0) continue;
862
+ generatedColumn += nums[0];
863
+ if (nums.length >= 4) {
864
+ sourceIndex += nums[1] ?? 0;
865
+ originalLine += nums[2] ?? 0;
866
+ originalColumn += nums[3] ?? 0;
867
+ segs.push({
868
+ generatedColumn,
869
+ source: sources[sourceIndex] ?? USER_ENTRY,
870
+ originalLine,
871
+ originalColumn
872
+ });
873
+ }
874
+ }
875
+ }
876
+ decoded.push(segs);
877
+ }
878
+ return decoded;
879
+ }
880
+ function originalPositionFor(map, line, column) {
881
+ const decoded = decodeMappings(map);
882
+ for (let i = line - 1; i >= 0; i--) {
883
+ const segs = decoded[i];
884
+ if (!segs || segs.length === 0) continue;
885
+ const col = i === line - 1 ? column - 1 : Number.POSITIVE_INFINITY;
886
+ let best = segs[0];
887
+ for (const seg of segs) {
888
+ if (seg.generatedColumn <= col) best = seg;
889
+ else break;
890
+ }
891
+ if (!best) continue;
892
+ return { source: best.source, line: best.originalLine + 1, column: best.originalColumn };
893
+ }
894
+ return void 0;
895
+ }
896
+ function extractInlineSourceMap(code) {
897
+ const m = code.match(SOURCEMAP_RE);
898
+ if (!m?.[1]) return void 0;
899
+ try {
900
+ return JSON.parse(Buffer.from(m[1], "base64").toString("utf8"));
901
+ } catch {
902
+ return void 0;
903
+ }
904
+ }
905
+ function remapErrorStack(stack, bundledCode) {
906
+ const map = extractInlineSourceMap(bundledCode);
907
+ if (!map?.mappings) return stack;
908
+ return stack.replace(/(blob:[^\s)]+?):(\d+):(\d+)/g, (full, _url, line, col) => {
909
+ const orig = originalPositionFor(map, Number(line), Number(col));
910
+ if (!orig) return full;
911
+ return `${orig.source}:${orig.line}:${orig.column}`;
912
+ });
511
913
  }
512
914
  export {
513
915
  APP_ID_HEADER,
514
916
  AUTH_HEADER,
515
917
  BEARER_PREFIX,
918
+ BundleError,
516
919
  CODEX_MARKER_BEGIN,
517
920
  CODEX_MARKER_END,
518
921
  CONFIG_FILE_NAME,
519
922
  ConfigError,
520
923
  DEFAULT_BASE_URL,
924
+ DEFAULT_BUNDLE_OUT,
925
+ DEFAULT_OSS_URL,
926
+ EASYTWIN_TYPES_DIR,
521
927
  ENDPOINTS,
928
+ EXAMPLE_SCENE_FILE,
522
929
  EasyTwinApiError,
523
930
  EasyTwinClient,
524
931
  GITIGNORE_ENTRY,
525
932
  GITIGNORE_FILE_NAME,
526
933
  META_FILE_NAME,
934
+ MINIMAL_TSCONFIG,
935
+ MOCK_APP_ID,
936
+ MOCK_APP_SECRET,
937
+ MOCK_SCENE_NAME,
938
+ RUNTIME_MODULE,
527
939
  SKILL_NAMES,
940
+ TEST_BASE_URL,
941
+ TSCONFIG_PATHS_HINT,
942
+ USER_ENTRY,
528
943
  appendGitignore,
529
944
  buildMultipartBody,
945
+ buildRuntimeTypesContent,
946
+ bundleUserCode,
530
947
  collectFiles,
531
948
  configFilePath,
949
+ deriveExampleSceneId,
532
950
  detectSkillsStatus,
951
+ exampleSceneSummary,
952
+ extractInlineSourceMap,
533
953
  initConfig,
954
+ isMockCredentials,
534
955
  listScenes,
535
956
  loadConfig,
957
+ loadExampleScene,
536
958
  normalizeSceneDetail,
537
959
  normalizeSceneList,
538
960
  normalizeTargets,
539
961
  parseConfig,
962
+ parseSceneStructure,
540
963
  pullScene,
541
964
  readConfigFile,
542
965
  readDevkitVersion,
966
+ remapErrorStack,
543
967
  resolveConfig,
968
+ resolveExampleScenePath,
969
+ resolveRuntimeTypesSourceFile,
544
970
  resolveSkillsSourceDir,
545
971
  saveSceneJson,
546
972
  syncSkills,
973
+ typesMissingHint,
547
974
  uploadDirectory,
548
975
  validateConfigShape,
549
976
  writeConfigFile