@dimina-kit/devtools 0.3.1-dev.20260517100756 → 0.3.1-dev.20260518114324

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.
Files changed (83) hide show
  1. package/README.md +138 -7
  2. package/dist/main/app/app.js +8 -0
  3. package/dist/main/index.bundle.js +374 -58
  4. package/dist/main/ipc/projects.d.ts +3 -1
  5. package/dist/main/ipc/projects.js +88 -4
  6. package/dist/main/ipc/session.js +9 -1
  7. package/dist/main/ipc/settings.js +8 -6
  8. package/dist/main/menu/index.js +11 -0
  9. package/dist/main/services/projects/builtin-templates.d.ts +3 -0
  10. package/dist/main/services/projects/builtin-templates.js +24 -0
  11. package/dist/main/services/projects/create-project-service.d.ts +7 -0
  12. package/dist/main/services/projects/create-project-service.js +84 -0
  13. package/dist/main/services/projects/index.d.ts +7 -0
  14. package/dist/main/services/projects/index.js +9 -0
  15. package/dist/main/services/projects/local-provider.d.ts +3 -0
  16. package/dist/main/services/projects/local-provider.js +25 -0
  17. package/dist/main/services/projects/templates.d.ts +21 -0
  18. package/dist/main/services/projects/templates.js +39 -0
  19. package/dist/main/services/projects/thumbnail.d.ts +8 -0
  20. package/dist/main/services/projects/thumbnail.js +37 -0
  21. package/dist/main/services/projects/types.d.ts +118 -0
  22. package/dist/main/services/projects/types.js +19 -0
  23. package/dist/main/services/settings/index.d.ts +7 -0
  24. package/dist/main/services/settings/index.js +4 -0
  25. package/dist/main/services/workbench-context.d.ts +35 -0
  26. package/dist/main/services/workbench-context.js +9 -0
  27. package/dist/main/services/workspace/workspace-service.d.ts +9 -7
  28. package/dist/main/services/workspace/workspace-service.js +81 -9
  29. package/dist/main/utils/sender-policy.d.ts +7 -5
  30. package/dist/main/utils/sender-policy.js +8 -6
  31. package/dist/main/windows/main-window/create.js +1 -1
  32. package/dist/preload/runtime/custom-apis.js +32 -3
  33. package/dist/preload/windows/simulator.js +26 -5
  34. package/dist/renderer/assets/index-Czz_bp6b.js +46 -0
  35. package/dist/renderer/assets/{input-C5M5Wxn6.js → input-DqVQNCx_.js} +2 -2
  36. package/dist/renderer/assets/{ipc-transport-CUsl-fS2.js → ipc-transport-CCHrCtDr.js} +2 -2
  37. package/dist/renderer/assets/ipc-transport-DqbRdZNO.css +1 -0
  38. package/dist/renderer/assets/{popover-BSBPVO0o.js → popover-BRg4XI_w.js} +2 -2
  39. package/dist/renderer/assets/{select-B1tF3UAc.js → select-CvILHHZU.js} +2 -2
  40. package/dist/renderer/assets/{settings-7w54H6p-.js → settings-BFNfnYvU.js} +2 -2
  41. package/dist/renderer/assets/{settings-api-BjJalghB.js → settings-api-DOAgmBst.js} +2 -2
  42. package/dist/renderer/assets/{workbenchSettings-q2_-nc8t.js → workbenchSettings-DjVMgMMH.js} +2 -2
  43. package/dist/renderer/entries/main/index.html +5 -5
  44. package/dist/renderer/entries/popover/index.html +5 -5
  45. package/dist/renderer/entries/settings/index.html +5 -5
  46. package/dist/renderer/entries/workbench-settings/index.html +4 -4
  47. package/dist/shared/ipc-channels.d.ts +20 -1
  48. package/dist/shared/ipc-channels.js +27 -0
  49. package/dist/shared/ipc-schemas.d.ts +5 -0
  50. package/dist/shared/ipc-schemas.js +5 -0
  51. package/dist/shared/types.d.ts +80 -0
  52. package/package.json +9 -4
  53. package/templates/blank/app.js +6 -0
  54. package/templates/blank/app.json +11 -0
  55. package/templates/blank/app.wxss +5 -0
  56. package/templates/blank/pages/index/index.js +6 -0
  57. package/templates/blank/pages/index/index.json +3 -0
  58. package/templates/blank/pages/index/index.wxml +3 -0
  59. package/templates/blank/pages/index/index.wxss +11 -0
  60. package/templates/blank/project.config.json +13 -0
  61. package/templates/blank/sitemap.json +9 -0
  62. package/templates/taro-todo/app.js +1 -0
  63. package/templates/taro-todo/app.json +1 -0
  64. package/templates/taro-todo/app.wxss +1 -0
  65. package/templates/taro-todo/base.wxml +851 -0
  66. package/templates/taro-todo/common.js +1 -0
  67. package/templates/taro-todo/comp.js +1 -0
  68. package/templates/taro-todo/comp.json +1 -0
  69. package/templates/taro-todo/comp.wxml +2 -0
  70. package/templates/taro-todo/custom-wrapper.js +1 -0
  71. package/templates/taro-todo/custom-wrapper.json +1 -0
  72. package/templates/taro-todo/custom-wrapper.wxml +4 -0
  73. package/templates/taro-todo/pages/index/index.js +2 -0
  74. package/templates/taro-todo/pages/index/index.json +1 -0
  75. package/templates/taro-todo/pages/index/index.wxml +2 -0
  76. package/templates/taro-todo/pages/index/index.wxss +1 -0
  77. package/templates/taro-todo/project.config.json +44 -0
  78. package/templates/taro-todo/runtime.js +1 -0
  79. package/templates/taro-todo/taro.js +2 -0
  80. package/templates/taro-todo/utils.wxs +39 -0
  81. package/templates/taro-todo/vendors.js +2 -0
  82. package/dist/renderer/assets/index-DW_0thr-.js +0 -46
  83. package/dist/renderer/assets/ipc-transport-CltRuvhi.css +0 -1
@@ -17,7 +17,8 @@ var DEFAULTS = {
17
17
  compile: {
18
18
  watch: true
19
19
  },
20
- theme: "system"
20
+ theme: "system",
21
+ lastCreateBaseDir: null
21
22
  };
22
23
  function getSettingsFile() {
23
24
  return path.join(app.getPath("userData"), "dimina-workbench-settings.json");
@@ -38,7 +39,8 @@ function loadWorkbenchSettings() {
38
39
  compile: {
39
40
  watch: data.compile?.watch ?? DEFAULTS.compile.watch
40
41
  },
41
- theme
42
+ theme,
43
+ lastCreateBaseDir: typeof data.lastCreateBaseDir === "string" ? data.lastCreateBaseDir : DEFAULTS.lastCreateBaseDir
42
44
  };
43
45
  } catch {
44
46
  return {
@@ -73,9 +75,9 @@ function setupCdpPort() {
73
75
  }
74
76
 
75
77
  // src/main/app/app.ts
76
- import { app as app11, BrowserWindow as BrowserWindow3, nativeImage } from "electron";
77
- import fs5 from "fs";
78
- import path8 from "path";
78
+ import { app as app13, BrowserWindow as BrowserWindow3, nativeImage } from "electron";
79
+ import fs7 from "fs";
80
+ import path12 from "path";
79
81
 
80
82
  // src/main/utils/paths.ts
81
83
  import fs2 from "fs";
@@ -200,7 +202,7 @@ function configureSimulatorSession(simulatorPreloadPath) {
200
202
  headers["access-control-allow-headers"] = ["*"];
201
203
  headers["access-control-allow-methods"] = ["*"];
202
204
  headers["Cross-Origin-Opener-Policy"] = ["same-origin"];
203
- headers["Cross-Origin-Embedder-Policy"] = ["require-corp"];
205
+ headers["Cross-Origin-Embedder-Policy"] = ["credentialless"];
204
206
  callback({ responseHeaders: headers });
205
207
  });
206
208
  }
@@ -441,12 +443,22 @@ var ProjectChannel = {
441
443
  GetPages: "project:getPages",
442
444
  GetCompileConfig: "project:getCompileConfig",
443
445
  SaveCompileConfig: "project:saveCompileConfig",
444
- Status: "project:status"
446
+ Status: "project:status",
447
+ CaptureThumbnail: "project:captureThumbnail",
448
+ GetThumbnail: "project:getThumbnail"
445
449
  };
446
450
  var ProjectsChannel = {
447
451
  List: "projects:list",
448
452
  Add: "projects:add",
449
- Remove: "projects:remove"
453
+ Remove: "projects:remove",
454
+ /** Phase 3: merged + sanitized template catalog for the create-project dialog. */
455
+ ListTemplates: "projects:listTemplates",
456
+ /** Phase 3: host-supplied create-project dialog hook (returns input or null). */
457
+ OpenCreateDialog: "projects:openCreateDialog",
458
+ /** Phase 3: server-side scaffold + register (delegates to create-project-service). */
459
+ Create: "projects:create",
460
+ /** Default values used to pre-fill the create-project dialog (baseDir). */
461
+ GetCreateDefaults: "projects:getCreateDefaults"
450
462
  };
451
463
  var DialogChannel = {
452
464
  OpenDirectory: "dialog:openDirectory"
@@ -867,6 +879,9 @@ function createWindowService(mainWindow) {
867
879
  };
868
880
  }
869
881
 
882
+ // src/main/services/workspace/workspace-service.ts
883
+ import { webContents as webContents2 } from "electron";
884
+
870
885
  // src/main/services/projects/project-repository.ts
871
886
  import { app as app6 } from "electron";
872
887
  import path7 from "path";
@@ -955,9 +970,6 @@ function validateProjectDir(dirPath) {
955
970
  }
956
971
  return null;
957
972
  }
958
- function hasProject(dirPath) {
959
- return load().some((p) => p.path === dirPath);
960
- }
961
973
  function addProject(dirPath) {
962
974
  const projects = load();
963
975
  let name = path7.basename(dirPath);
@@ -1054,6 +1066,13 @@ function updateProjectSettings(projectPath, patch) {
1054
1066
  );
1055
1067
  }
1056
1068
 
1069
+ // src/main/services/projects/types.ts
1070
+ var DEFAULT_COMPILE_CONFIG = {
1071
+ startPage: "",
1072
+ scene: DEFAULT_SCENE,
1073
+ queryParams: []
1074
+ };
1075
+
1057
1076
  // src/main/services/workspace/workspace-service.ts
1058
1077
  function createWorkspaceService(ctx) {
1059
1078
  let currentSession = null;
@@ -1092,17 +1111,31 @@ function createWorkspaceService(ctx) {
1092
1111
  );
1093
1112
  }
1094
1113
  }
1095
- return {
1114
+ const provider = ctx.projectsProvider ?? {
1096
1115
  listProjects: () => listProjects(),
1097
- addProject: (dirPath) => addProject(dirPath),
1098
- removeProject: (dirPath) => removeProject(dirPath),
1099
- hasProject: (dirPath) => hasProject(dirPath),
1100
- validateProjectDir: (dirPath) => validateProjectDir(dirPath),
1116
+ addProject: (p) => addProject(p),
1117
+ removeProject: (p) => removeProject(p),
1118
+ validateProjectDir: (p) => validateProjectDir(p),
1119
+ updateLastOpened: (p) => updateLastOpened(p),
1120
+ getCompileConfig: (p) => getCompileConfig(p),
1121
+ saveCompileConfig: (p, c) => saveCompileConfig(p, c)
1122
+ };
1123
+ return {
1124
+ listProjects: async () => provider.listProjects(),
1125
+ addProject: async (dirPath) => provider.addProject(dirPath),
1126
+ removeProject: async (dirPath) => {
1127
+ await provider.removeProject(dirPath);
1128
+ },
1129
+ hasProject: async (dirPath) => {
1130
+ const projects = await provider.listProjects();
1131
+ return projects.some((p) => p.path === dirPath);
1132
+ },
1133
+ validateProjectDir: async (dirPath) => provider.validateProjectDir ? provider.validateProjectDir(dirPath) : null,
1101
1134
  async openProject(projectPath) {
1102
1135
  clearSimulatorServicewechatReferer();
1103
1136
  await disposeSession();
1104
1137
  currentProjectPath = "";
1105
- const dirError = validateProjectDir(projectPath);
1138
+ const dirError = provider.validateProjectDir ? await provider.validateProjectDir(projectPath) : null;
1106
1139
  if (dirError) {
1107
1140
  sendStatus("error", dirError);
1108
1141
  return { success: false, error: dirError };
@@ -1125,7 +1158,9 @@ function createWorkspaceService(ctx) {
1125
1158
  }
1126
1159
  currentSession = session2;
1127
1160
  currentProjectPath = projectPath;
1128
- bestEffort("updateLastOpened", () => updateLastOpened(projectPath));
1161
+ bestEffort("updateLastOpened", () => {
1162
+ if (provider.updateLastOpened) provider.updateLastOpened(projectPath);
1163
+ });
1129
1164
  bestEffort("sendStatus", () => sendStatus("ready", "\u7F16\u8BD1\u5B8C\u6210"));
1130
1165
  bestEffort("applyReferer", () => applyRefererFromSession(session2));
1131
1166
  return {
@@ -1143,14 +1178,134 @@ function createWorkspaceService(ctx) {
1143
1178
  getSession: () => currentSession,
1144
1179
  getProjectPath: () => currentProjectPath,
1145
1180
  hasActiveSession: () => currentSession !== null,
1181
+ async captureThumbnail(projectPath) {
1182
+ const simWcId = ctx.views.getSimulatorWebContentsId();
1183
+ if (!simWcId) return null;
1184
+ const wc = webContents2.fromId(simWcId);
1185
+ if (!wc || wc.isDestroyed()) return null;
1186
+ try {
1187
+ const image = await wc.capturePage();
1188
+ const dataUrl = `data:image/png;base64,${image.toPNG().toString("base64")}`;
1189
+ if (provider.saveThumbnail) {
1190
+ await provider.saveThumbnail(projectPath, dataUrl);
1191
+ }
1192
+ return dataUrl;
1193
+ } catch {
1194
+ return null;
1195
+ }
1196
+ },
1197
+ async getThumbnail(projectPath) {
1198
+ if (provider.getThumbnail) {
1199
+ return await provider.getThumbnail(projectPath) ?? null;
1200
+ }
1201
+ return null;
1202
+ },
1203
+ // `getProjectPages` reads the project's own `app.json` from disk and is
1204
+ // independent of the project registry — it stays on the repo helper.
1146
1205
  getProjectPages: (projectPath) => getProjectPages(projectPath),
1147
- getCompileConfig: (projectPath) => getCompileConfig(projectPath),
1148
- saveCompileConfig: (projectPath, config) => saveCompileConfig(projectPath, config),
1206
+ getCompileConfig: async (projectPath) => provider.getCompileConfig ? await provider.getCompileConfig(projectPath) : DEFAULT_COMPILE_CONFIG,
1207
+ saveCompileConfig: async (projectPath, config) => {
1208
+ if (provider.saveCompileConfig) {
1209
+ await provider.saveCompileConfig(projectPath, config);
1210
+ }
1211
+ },
1212
+ // Per-project settings (uploadWithSourceMap etc.) live in the project's
1213
+ // own `project.config.json`, not the registry — keep direct repo calls.
1149
1214
  getProjectSettings: (projectPath) => getProjectSettings(projectPath),
1150
1215
  updateProjectSettings: (projectPath, patch) => updateProjectSettings(projectPath, patch)
1151
1216
  };
1152
1217
  }
1153
1218
 
1219
+ // src/main/services/projects/thumbnail.ts
1220
+ import { createHash } from "crypto";
1221
+ import fs4 from "fs";
1222
+ import path8 from "path";
1223
+ import { app as app7 } from "electron";
1224
+ function getThumbnailDir() {
1225
+ return path8.join(app7.getPath("userData"), "thumbnails");
1226
+ }
1227
+ function hashProjectPath(projectPath) {
1228
+ return createHash("sha256").update(projectPath).digest("hex").slice(0, 16);
1229
+ }
1230
+ function getThumbnailPath(projectPath) {
1231
+ return path8.join(getThumbnailDir(), `${hashProjectPath(projectPath)}.png`);
1232
+ }
1233
+ var DATA_URL_PNG_PREFIX = "data:image/png;base64,";
1234
+ function saveThumbnailFromDataUrl(projectPath, imageDataUrl) {
1235
+ if (!imageDataUrl.startsWith(DATA_URL_PNG_PREFIX)) return;
1236
+ const png = Buffer.from(imageDataUrl.slice(DATA_URL_PNG_PREFIX.length), "base64");
1237
+ fs4.mkdirSync(getThumbnailDir(), { recursive: true });
1238
+ fs4.writeFileSync(getThumbnailPath(projectPath), png);
1239
+ }
1240
+ function loadThumbnail(projectPath) {
1241
+ const filePath = getThumbnailPath(projectPath);
1242
+ try {
1243
+ const buf = fs4.readFileSync(filePath);
1244
+ return `${DATA_URL_PNG_PREFIX}${buf.toString("base64")}`;
1245
+ } catch {
1246
+ return null;
1247
+ }
1248
+ }
1249
+
1250
+ // src/main/services/projects/local-provider.ts
1251
+ function createLocalProjectsProvider() {
1252
+ return {
1253
+ listProjects: () => listProjects(),
1254
+ addProject: (dirPath) => addProject(dirPath),
1255
+ removeProject: (dirPath) => removeProject(dirPath),
1256
+ validateProjectDir: (dirPath) => validateProjectDir(dirPath),
1257
+ updateLastOpened: (dirPath) => updateLastOpened(dirPath),
1258
+ getCompileConfig: (dirPath) => getCompileConfig(dirPath),
1259
+ saveCompileConfig: (dirPath, cfg) => saveCompileConfig(dirPath, cfg),
1260
+ saveThumbnail: (dirPath, dataUrl) => saveThumbnailFromDataUrl(dirPath, dataUrl),
1261
+ getThumbnail: (dirPath) => loadThumbnail(dirPath)
1262
+ };
1263
+ }
1264
+
1265
+ // src/main/services/projects/templates.ts
1266
+ function resolveTemplates(builtin, injected, mode) {
1267
+ let kept;
1268
+ if (mode === "none") {
1269
+ kept = [];
1270
+ } else if (mode === "all") {
1271
+ kept = [...builtin];
1272
+ } else {
1273
+ const byId = new Map(builtin.map((t) => [t.id, t]));
1274
+ kept = [];
1275
+ for (const id of mode) {
1276
+ const t = byId.get(id);
1277
+ if (t) kept.push(t);
1278
+ }
1279
+ }
1280
+ const injectedIds = new Set(injected.map((t) => t.id));
1281
+ const remaining = kept.filter((t) => !injectedIds.has(t.id));
1282
+ return [...injected, ...remaining];
1283
+ }
1284
+ function sanitizeTemplates(templates) {
1285
+ return templates.map((t) => {
1286
+ const { generate: _generate, ...rest } = t;
1287
+ return rest;
1288
+ });
1289
+ }
1290
+
1291
+ // src/main/services/projects/builtin-templates.ts
1292
+ import path9 from "node:path";
1293
+ var TEMPLATES_DIR = path9.join(devtoolsPackageRoot, "templates");
1294
+ var BUILTIN_TEMPLATES = [
1295
+ {
1296
+ id: "blank",
1297
+ name: "Blank",
1298
+ description: "\u6700\u5C0F\u9AA8\u67B6\uFF1Aapp.* + \u4E00\u4E2A index \u9875\u9762",
1299
+ source: { type: "directory", path: path9.join(TEMPLATES_DIR, "blank") }
1300
+ },
1301
+ {
1302
+ id: "taro-todo",
1303
+ name: "Taro Todo",
1304
+ description: "Taro \u7F16\u8BD1\u4EA7\u7269\u7684 Todo \u793A\u4F8B",
1305
+ source: { type: "directory", path: path9.join(TEMPLATES_DIR, "taro-todo") }
1306
+ }
1307
+ ];
1308
+
1154
1309
  // src/main/services/workbench-context.ts
1155
1310
  function hasBuiltinPanel(ctx, panelId) {
1156
1311
  return ctx.panels.includes(panelId);
@@ -1177,6 +1332,13 @@ function createWorkbenchContext(opts) {
1177
1332
  ctx.windows = createWindowService(ctx.mainWindow);
1178
1333
  ctx.views = createViewManager(ctx);
1179
1334
  ctx.notify = createRendererNotifier(ctx);
1335
+ ctx.projectsProvider = opts.projectsProvider ?? createLocalProjectsProvider();
1336
+ ctx.projectTemplates = resolveTemplates(
1337
+ BUILTIN_TEMPLATES,
1338
+ opts.projectTemplates ?? [],
1339
+ opts.builtinTemplates ?? "all"
1340
+ );
1341
+ ctx.customCreateProjectDialog = opts.customCreateProjectDialog;
1180
1342
  ctx.workspace = createWorkspaceService(ctx);
1181
1343
  ctx.senderPolicy = createWorkbenchSenderPolicy(ctx);
1182
1344
  return ctx;
@@ -1208,6 +1370,17 @@ function installAppMenu(ctx) {
1208
1370
  { role: "quit" }
1209
1371
  ]
1210
1372
  },
1373
+ {
1374
+ label: "\u9879\u76EE",
1375
+ submenu: [
1376
+ {
1377
+ label: "\u6253\u5F00\u9879\u76EE",
1378
+ click: () => {
1379
+ ctx.notify.windowNavigateBack();
1380
+ }
1381
+ }
1382
+ ]
1383
+ },
1211
1384
  {
1212
1385
  label: "\u7F16\u8F91",
1213
1386
  submenu: [
@@ -1249,7 +1422,7 @@ function installAppMenu(ctx) {
1249
1422
  }
1250
1423
 
1251
1424
  // src/main/ipc/app.ts
1252
- import fs4 from "fs";
1425
+ import fs5 from "fs";
1253
1426
 
1254
1427
  // src/main/utils/ipc-registry.ts
1255
1428
  import { ipcMain } from "electron";
@@ -1355,7 +1528,7 @@ var IpcRegistry = class {
1355
1528
  // src/main/ipc/app.ts
1356
1529
  function registerAppIpc(ctx) {
1357
1530
  return new IpcRegistry(ctx.senderPolicy).handle(AppChannel.GetPreloadPath, () => {
1358
- return `file://${fs4.realpathSync(ctx.preloadPath)}`;
1531
+ return `file://${fs5.realpathSync(ctx.preloadPath)}`;
1359
1532
  }).handle(AppChannel.GetBranding, async () => {
1360
1533
  if (ctx.brandingProvider) return ctx.brandingProvider();
1361
1534
  return { appName: ctx.appName };
@@ -1408,7 +1581,8 @@ var WorkbenchSettingsSaveSchema = z.tuple([
1408
1581
  compile: z.looseObject({
1409
1582
  watch: z.boolean()
1410
1583
  }),
1411
- theme: z.enum(["system", "light", "dark"])
1584
+ theme: z.enum(["system", "light", "dark"]),
1585
+ lastCreateBaseDir: z.union([z.string(), z.null()])
1412
1586
  })
1413
1587
  ]);
1414
1588
  var WorkbenchSettingsSetThemeSchema = z.tuple([
@@ -1424,6 +1598,8 @@ var SettingsProjectSettingsChangedSchema = z.tuple([
1424
1598
  })
1425
1599
  ]);
1426
1600
  var PanelSelectSchema = z.tuple([z.string().min(1).max(200)]);
1601
+ var ProjectCaptureThumbnailSchema = z.tuple([AbsolutePath]);
1602
+ var ProjectGetThumbnailSchema = z.tuple([AbsolutePath]);
1427
1603
 
1428
1604
  // src/main/services/simulator/custom-apis.ts
1429
1605
  function createSimulatorApiRegistry() {
@@ -1478,7 +1654,7 @@ function registerSimulatorIpc(ctx) {
1478
1654
  }
1479
1655
 
1480
1656
  // src/main/ipc/panels.ts
1481
- import { webContents as webContents2 } from "electron";
1657
+ import { webContents as webContents3 } from "electron";
1482
1658
  var BUILTIN_PANELS = [
1483
1659
  { id: "wxml", label: "WXML" },
1484
1660
  { id: "appdata", label: "AppData" },
@@ -1491,7 +1667,7 @@ function registerPanelsIpc(ctx) {
1491
1667
  const [expression] = validate(PanelChannel.Eval, PanelEvalSchema, args);
1492
1668
  const simWcId = ctx.views.getSimulatorWebContentsId();
1493
1669
  if (!ctx.workspace.hasActiveSession() || simWcId == null) return void 0;
1494
- const sim = webContents2.fromId(simWcId);
1670
+ const sim = webContents3.fromId(simWcId);
1495
1671
  if (!sim || sim.isDestroyed()) return void 0;
1496
1672
  try {
1497
1673
  return await sim.executeJavaScript(expression);
@@ -1528,7 +1704,74 @@ var popoverModule = {
1528
1704
  };
1529
1705
 
1530
1706
  // src/main/ipc/projects.ts
1531
- import { dialog } from "electron";
1707
+ import { app as app8, dialog } from "electron";
1708
+ import path11 from "path";
1709
+
1710
+ // src/main/services/projects/create-project-service.ts
1711
+ import fs6 from "node:fs";
1712
+ import path10 from "node:path";
1713
+ var DEFAULT_TEMPLATE_ID = "blank";
1714
+ async function createProject(input, ctx) {
1715
+ const name = (input.name ?? "").trim();
1716
+ if (name.length === 0) {
1717
+ throw new Error("Project name cannot be empty");
1718
+ }
1719
+ const target = input.path;
1720
+ if (!target || typeof target !== "string") {
1721
+ throw new Error("Project path is required");
1722
+ }
1723
+ if (fs6.existsSync(target)) {
1724
+ const stat = fs6.statSync(target);
1725
+ if (!stat.isDirectory()) {
1726
+ throw new Error(`Project path exists and is not a directory: ${target}`);
1727
+ }
1728
+ const entries = fs6.readdirSync(target);
1729
+ if (entries.length > 0) {
1730
+ throw new Error(
1731
+ `Project path is not empty (refusing to overwrite): ${target}`
1732
+ );
1733
+ }
1734
+ }
1735
+ const templateId = input.templateId ?? DEFAULT_TEMPLATE_ID;
1736
+ const template = ctx.templates.find((t) => t.id === templateId);
1737
+ if (!template) {
1738
+ throw new Error(`Template not found: ${templateId}`);
1739
+ }
1740
+ if (!template.source && !template.generate) {
1741
+ throw new Error(
1742
+ `Template '${templateId}' has neither a source directory nor a generate function`
1743
+ );
1744
+ }
1745
+ fs6.mkdirSync(target, { recursive: true });
1746
+ if (template.generate) {
1747
+ await template.generate(target, { name });
1748
+ } else if (template.source) {
1749
+ if (!fs6.existsSync(template.source.path)) {
1750
+ throw new Error(
1751
+ `Template source missing on disk: ${template.source.path}`
1752
+ );
1753
+ }
1754
+ fs6.cpSync(template.source.path, target, {
1755
+ recursive: true,
1756
+ force: true
1757
+ });
1758
+ }
1759
+ const cfgPath = path10.join(target, "project.config.json");
1760
+ let cfg = {};
1761
+ if (fs6.existsSync(cfgPath)) {
1762
+ try {
1763
+ cfg = JSON.parse(fs6.readFileSync(cfgPath, "utf-8"));
1764
+ } catch {
1765
+ cfg = {};
1766
+ }
1767
+ }
1768
+ cfg.projectname = name;
1769
+ fs6.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2));
1770
+ const created = await ctx.projectsProvider.addProject(target);
1771
+ return created;
1772
+ }
1773
+
1774
+ // src/main/ipc/projects.ts
1532
1775
  function registerProjectsIpc(ctx) {
1533
1776
  return new IpcRegistry(ctx.senderPolicy).handle(ProjectsChannel.List, () => {
1534
1777
  return ctx.workspace.listProjects();
@@ -1540,7 +1783,7 @@ function registerProjectsIpc(ctx) {
1540
1783
  return result.canceled ? null : result.filePaths[0];
1541
1784
  }).handle(ProjectsChannel.Add, async (_event, ...args) => {
1542
1785
  const [dirPath] = validate(ProjectsChannel.Add, ProjectsAddSchema, args);
1543
- const dirError = ctx.workspace.validateProjectDir(dirPath);
1786
+ const dirError = await ctx.workspace.validateProjectDir(dirPath);
1544
1787
  if (dirError) {
1545
1788
  await dialog.showMessageBox(ctx.mainWindow, {
1546
1789
  type: "error",
@@ -1551,8 +1794,8 @@ function registerProjectsIpc(ctx) {
1551
1794
  });
1552
1795
  throw new Error(dirError);
1553
1796
  }
1554
- const duplicate = ctx.workspace.hasProject(dirPath);
1555
- const project = ctx.workspace.addProject(dirPath);
1797
+ const duplicate = await ctx.workspace.hasProject(dirPath);
1798
+ const project = await ctx.workspace.addProject(dirPath);
1556
1799
  if (duplicate) {
1557
1800
  await dialog.showMessageBox(ctx.mainWindow, {
1558
1801
  type: "info",
@@ -1566,8 +1809,60 @@ function registerProjectsIpc(ctx) {
1566
1809
  }).handle(ProjectsChannel.Remove, (_event, ...args) => {
1567
1810
  const [dirPath] = validate(ProjectsChannel.Remove, ProjectsRemoveSchema, args);
1568
1811
  return ctx.workspace.removeProject(dirPath);
1812
+ }).handle(ProjectsChannel.ListTemplates, () => {
1813
+ return sanitizeTemplates(ctx.projectTemplates ?? []);
1814
+ }).handle(ProjectsChannel.OpenCreateDialog, async () => {
1815
+ if (!ctx.customCreateProjectDialog) return null;
1816
+ const sanitized = sanitizeTemplates(ctx.projectTemplates ?? []);
1817
+ return await ctx.customCreateProjectDialog({
1818
+ parentWindow: ctx.mainWindow,
1819
+ templates: sanitized
1820
+ });
1821
+ }).handle(ProjectsChannel.Create, async (_event, ...args) => {
1822
+ const [raw] = args;
1823
+ if (!raw || typeof raw !== "object") {
1824
+ throw new Error("projects:create expects a CreateProjectInput object");
1825
+ }
1826
+ const input = raw;
1827
+ let project;
1828
+ try {
1829
+ project = await createProject(input, {
1830
+ templates: ctx.projectTemplates ?? [],
1831
+ projectsProvider: ctx.projectsProvider
1832
+ });
1833
+ } catch (err) {
1834
+ await dialog.showMessageBox(ctx.mainWindow, {
1835
+ type: "error",
1836
+ title: "\u521B\u5EFA\u9879\u76EE\u5931\u8D25",
1837
+ message: "\u65E0\u6CD5\u521B\u5EFA\u9879\u76EE",
1838
+ detail: err instanceof Error ? err.message : String(err),
1839
+ buttons: ["\u786E\u5B9A"]
1840
+ });
1841
+ throw err;
1842
+ }
1843
+ try {
1844
+ const settings = loadWorkbenchSettings();
1845
+ const newBase = path11.dirname(input.path);
1846
+ if (newBase && settings.lastCreateBaseDir !== newBase) {
1847
+ saveWorkbenchSettings({ ...settings, lastCreateBaseDir: newBase });
1848
+ }
1849
+ } catch (err) {
1850
+ console.warn("[projects:create] failed to persist lastCreateBaseDir", err);
1851
+ }
1852
+ return project;
1853
+ }).handle(ProjectsChannel.GetCreateDefaults, () => {
1854
+ const settings = loadWorkbenchSettings();
1855
+ const baseDir = settings.lastCreateBaseDir ?? safeAppPath("documents") ?? safeAppPath("home") ?? "";
1856
+ return { baseDir };
1569
1857
  });
1570
1858
  }
1859
+ function safeAppPath(name) {
1860
+ try {
1861
+ return app8.getPath(name);
1862
+ } catch {
1863
+ return null;
1864
+ }
1865
+ }
1571
1866
  var projectsModule = {
1572
1867
  setup: (ctx) => registerProjectsIpc(ctx)
1573
1868
  };
@@ -1596,6 +1891,20 @@ function registerSessionIpc(ctx) {
1596
1891
  return ctx.workspace.saveCompileConfig(projectPath, config);
1597
1892
  }).handle(ProjectChannel.Close, () => {
1598
1893
  return ctx.workspace.closeProject();
1894
+ }).handle(ProjectChannel.CaptureThumbnail, (_, ...args) => {
1895
+ const [projectPath] = validate(
1896
+ ProjectChannel.CaptureThumbnail,
1897
+ ProjectCaptureThumbnailSchema,
1898
+ args
1899
+ );
1900
+ return ctx.workspace.captureThumbnail(projectPath);
1901
+ }).handle(ProjectChannel.GetThumbnail, (_, ...args) => {
1902
+ const [projectPath] = validate(
1903
+ ProjectChannel.GetThumbnail,
1904
+ ProjectGetThumbnailSchema,
1905
+ args
1906
+ );
1907
+ return ctx.workspace.getThumbnail(projectPath);
1599
1908
  });
1600
1909
  }
1601
1910
  var sessionModule = {
@@ -1603,7 +1912,7 @@ var sessionModule = {
1603
1912
  };
1604
1913
 
1605
1914
  // src/main/ipc/settings.ts
1606
- import { app as app7 } from "electron";
1915
+ import { app as app9 } from "electron";
1607
1916
  function registerSettingsIpc(ctx) {
1608
1917
  return new IpcRegistry(ctx.senderPolicy).handle(WorkbenchSettingsChannel.Get, () => {
1609
1918
  return loadWorkbenchSettings();
@@ -1625,8 +1934,8 @@ function registerSettingsIpc(ctx) {
1625
1934
  applyTheme(theme);
1626
1935
  }).handle(WorkbenchSettingsChannel.GetCdpStatus, () => {
1627
1936
  const settings = loadWorkbenchSettings();
1628
- const switchValue = app7.commandLine.getSwitchValue("remote-debugging-port");
1629
- const implicitDevDefault = !app7.isPackaged && !settings.cdp.enabled && switchValue === "9222";
1937
+ const switchValue = app9.commandLine.getSwitchValue("remote-debugging-port");
1938
+ const implicitDevDefault = !app9.isPackaged && !settings.cdp.enabled && switchValue === "9222";
1630
1939
  return {
1631
1940
  configured: settings.cdp.enabled,
1632
1941
  port: settings.cdp.port,
@@ -1649,26 +1958,25 @@ function registerSettingsIpc(ctx) {
1649
1958
  const [visible] = validate(SettingsChannel.SetVisible, SettingsSetVisibleSchema, args);
1650
1959
  if (visible) {
1651
1960
  await ctx.views.showSettings();
1961
+ const projectPath = ctx.workspace.getProjectPath();
1652
1962
  ctx.notify.settingsInit({
1653
- projectPath: ctx.workspace.getProjectPath(),
1654
- config: ctx.workspace.getCompileConfig(ctx.workspace.getProjectPath()),
1655
- projectSettings: ctx.workspace.getProjectSettings(ctx.workspace.getProjectPath())
1963
+ projectPath,
1964
+ config: await ctx.workspace.getCompileConfig(projectPath),
1965
+ projectSettings: ctx.workspace.getProjectSettings(projectPath)
1656
1966
  });
1657
1967
  } else {
1658
1968
  ctx.views.hideSettings();
1659
1969
  ctx.notify.settingsClosed();
1660
1970
  }
1661
- }).on(SettingsChannel.ConfigChanged, (_, ...args) => {
1971
+ }).on(SettingsChannel.ConfigChanged, async (_, ...args) => {
1662
1972
  const [config] = validate(
1663
1973
  SettingsChannel.ConfigChanged,
1664
1974
  SettingsConfigChangedSchema,
1665
1975
  args
1666
1976
  );
1667
- if (ctx.workspace.getProjectPath()) {
1668
- ctx.workspace.saveCompileConfig(
1669
- ctx.workspace.getProjectPath(),
1670
- config
1671
- );
1977
+ const projectPath = ctx.workspace.getProjectPath();
1978
+ if (projectPath) {
1979
+ await ctx.workspace.saveCompileConfig(projectPath, config);
1672
1980
  }
1673
1981
  ctx.notify.settingsChanged(config);
1674
1982
  }).on(SettingsChannel.ProjectSettingsChanged, (_, ...args) => {
@@ -1710,11 +2018,11 @@ var simulatorModule = {
1710
2018
  import { WebSocketServer } from "ws";
1711
2019
 
1712
2020
  // src/main/services/automation/exec.ts
1713
- import { webContents as webContents3 } from "electron";
2021
+ import { webContents as webContents4 } from "electron";
1714
2022
  function getSimulator(ctx) {
1715
2023
  const simWcId = ctx.views.getSimulatorWebContentsId();
1716
2024
  if (!ctx.workspace.hasActiveSession() || simWcId == null) return null;
1717
- const sim = webContents3.fromId(simWcId);
2025
+ const sim = webContents4.fromId(simWcId);
1718
2026
  if (!sim || sim.isDestroyed()) return null;
1719
2027
  return sim;
1720
2028
  }
@@ -2880,7 +3188,7 @@ function startMcpServer(resolvedCdpPort, mcpPort) {
2880
3188
  }
2881
3189
 
2882
3190
  // src/main/services/simulator-storage/index.ts
2883
- import { app as app8, webContents as wcStatic } from "electron";
3191
+ import { app as app10, webContents as wcStatic } from "electron";
2884
3192
  function isSimulatorWebview(wc) {
2885
3193
  if (wc.isDestroyed()) return false;
2886
3194
  if (wc.getType() !== "webview") return false;
@@ -3130,9 +3438,9 @@ function setupSimulatorStorage(host, options) {
3130
3438
  wcSubs.delete(wc);
3131
3439
  });
3132
3440
  };
3133
- app8.on("web-contents-created", onWcCreated);
3441
+ app10.on("web-contents-created", onWcCreated);
3134
3442
  registry.add(() => {
3135
- app8.removeListener("web-contents-created", onWcCreated);
3443
+ app10.removeListener("web-contents-created", onWcCreated);
3136
3444
  });
3137
3445
  registry.add(async () => {
3138
3446
  const subs = Array.from(wcSubs.values());
@@ -3193,7 +3501,7 @@ async function clearElementInspection() {
3193
3501
  }
3194
3502
 
3195
3503
  // src/main/services/update/update-manager.ts
3196
- import { app as app9, shell as shell3 } from "electron";
3504
+ import { app as app11, shell as shell3 } from "electron";
3197
3505
  var UpdateManager = class {
3198
3506
  checker;
3199
3507
  mainWindow;
@@ -3206,7 +3514,7 @@ var UpdateManager = class {
3206
3514
  constructor(opts) {
3207
3515
  this.checker = opts.checker;
3208
3516
  this.mainWindow = opts.mainWindow;
3209
- this.getCurrentVersion = opts.getCurrentVersion ?? (() => app9.getVersion());
3517
+ this.getCurrentVersion = opts.getCurrentVersion ?? (() => app11.getVersion());
3210
3518
  this.ipc = new IpcRegistry(opts.senderPolicy);
3211
3519
  this.registerIpc();
3212
3520
  this.startPeriodicCheck(opts.checkInterval ?? 60 * 60 * 1e3, opts.initialDelay ?? 5e3);
@@ -3247,7 +3555,7 @@ var UpdateManager = class {
3247
3555
  install() {
3248
3556
  if (this.downloadedPath) {
3249
3557
  shell3.openPath(this.downloadedPath);
3250
- app9.quit();
3558
+ app11.quit();
3251
3559
  }
3252
3560
  }
3253
3561
  /**
@@ -3280,7 +3588,7 @@ var UpdateManager = class {
3280
3588
  };
3281
3589
 
3282
3590
  // src/main/services/update/github-release-checker.ts
3283
- import { app as app10 } from "electron";
3591
+ import { app as app12 } from "electron";
3284
3592
 
3285
3593
  // src/main/app/app.ts
3286
3594
  var DEFAULT_MODULES = {
@@ -3329,7 +3637,7 @@ async function disposeContext(ctx) {
3329
3637
  function createConfiguredMainWindow(config, rendererDir2) {
3330
3638
  const mainWindow = createMainWindow({
3331
3639
  title: config.appName ?? "Dimina DevTools",
3332
- indexHtml: path8.join(rendererDir2, "entries/main/index.html"),
3640
+ indexHtml: path12.join(rendererDir2, "entries/main/index.html"),
3333
3641
  width: config.window?.width,
3334
3642
  height: config.window?.height,
3335
3643
  minWidth: config.window?.minWidth,
@@ -3356,7 +3664,15 @@ function createContext(config, mainWindow, rendererDir2) {
3356
3664
  appName: config.appName,
3357
3665
  apiNamespaces: config.apiNamespaces,
3358
3666
  brandingProvider: config.brandingProvider,
3359
- toolbarActions: config.toolbarActions
3667
+ toolbarActions: config.toolbarActions,
3668
+ // The host-supplied ProjectsProvider / template types in `shared/types`
3669
+ // are structurally compatible with the main-process equivalents —
3670
+ // these casts are safe; we re-narrow at the workspace-service /
3671
+ // create-project-service boundary.
3672
+ projectsProvider: config.projectsProvider,
3673
+ projectTemplates: config.projectTemplates,
3674
+ builtinTemplates: config.builtinTemplates,
3675
+ customCreateProjectDialog: config.customCreateProjectDialog
3360
3676
  });
3361
3677
  }
3362
3678
  function registerBuiltinModules(config, context) {
@@ -3384,7 +3700,7 @@ async function setupAutomation(instance) {
3384
3700
  function setupMcp() {
3385
3701
  const settings = loadWorkbenchSettings();
3386
3702
  if (!settings.mcp.enabled) return null;
3387
- const cdpPortSwitch = app11.commandLine.getSwitchValue("remote-debugging-port");
3703
+ const cdpPortSwitch = app13.commandLine.getSwitchValue("remote-debugging-port");
3388
3704
  const cdpPort2 = cdpPortSwitch ? parseInt(cdpPortSwitch, 10) : settings.cdp.port;
3389
3705
  return startMcpServer(cdpPort2, settings.mcp.port);
3390
3706
  }
@@ -3405,12 +3721,12 @@ function wireAppWindowEvents(config, instance) {
3405
3721
  });
3406
3722
  }
3407
3723
  function enableDevRendererAutoReload(rendererDir2) {
3408
- if (app11.isPackaged) {
3724
+ if (app13.isPackaged) {
3409
3725
  return toDisposable(() => {
3410
3726
  });
3411
3727
  }
3412
3728
  let reloadTimer = null;
3413
- const watcher = fs5.watch(rendererDir2, { recursive: true }, () => {
3729
+ const watcher = fs7.watch(rendererDir2, { recursive: true }, () => {
3414
3730
  if (reloadTimer) clearTimeout(reloadTimer);
3415
3731
  reloadTimer = setTimeout(() => {
3416
3732
  for (const win of BrowserWindow3.getAllWindows()) {
@@ -3432,7 +3748,7 @@ function createWorkbenchApp(config = {}) {
3432
3748
  setupCdpPort();
3433
3749
  async function setup() {
3434
3750
  if (setupPromise) return setupPromise;
3435
- setupPromise = app11.whenReady().then(async () => {
3751
+ setupPromise = app13.whenReady().then(async () => {
3436
3752
  applyTheme(loadWorkbenchSettings().theme);
3437
3753
  const rendererDir2 = config.rendererDir ?? rendererDir;
3438
3754
  const mainWindow = createConfiguredMainWindow(config, rendererDir2);
@@ -3496,7 +3812,7 @@ function createWorkbenchApp(config = {}) {
3496
3812
 
3497
3813
  // src/main/windows/settings-window/create.ts
3498
3814
  import { BrowserWindow as BrowserWindow4 } from "electron";
3499
- import path9 from "path";
3815
+ import path13 from "path";
3500
3816
  async function createSettingsWindow(parent, rendererDir2) {
3501
3817
  const win = new BrowserWindow4({
3502
3818
  width: 420,
@@ -3514,7 +3830,7 @@ async function createSettingsWindow(parent, rendererDir2) {
3514
3830
  }
3515
3831
  });
3516
3832
  applyNavigationHardening(win.webContents, rendererDir2);
3517
- await win.loadFile(path9.join(rendererDir2, "entries/workbench-settings/index.html"));
3833
+ await win.loadFile(path13.join(rendererDir2, "entries/workbench-settings/index.html"));
3518
3834
  return win;
3519
3835
  }
3520
3836