@adep/cli 0.4.4 → 0.4.6

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 (46) hide show
  1. package/README.md +107 -11
  2. package/dist/index.js +33802 -27354
  3. package/dist/vite.js +247 -17
  4. package/dist/worker-entry.js +163 -1
  5. package/package.json +7 -6
  6. package/templates/app-desktop/README.md +67 -2
  7. package/templates/app-desktop/identity-store.mjs +309 -0
  8. package/templates/app-desktop/identity.mjs +517 -0
  9. package/templates/app-desktop/main.cjs +17 -0
  10. package/templates/app-desktop/main.mjs +218 -2
  11. package/templates/app-desktop/offline-runtime.mjs +72 -3
  12. package/templates/app-desktop/package.json +10 -6
  13. package/templates/app-desktop/recovery-ui.mjs +300 -0
  14. package/templates/app-desktop/sync-loop.mjs +137 -0
  15. package/templates/app-mobile/README.md +47 -9
  16. package/templates/app-mobile/package.json +2 -1
  17. package/templates/app-mobile/plugins/adep-bridge/Package.swift +5 -1
  18. package/templates/app-mobile/plugins/adep-bridge/android/build.gradle +34 -3
  19. package/templates/app-mobile/plugins/adep-bridge/android/src/main/AndroidManifest.xml +12 -5
  20. package/templates/app-mobile/plugins/adep-bridge/android/src/main/java/com/adep/bridge/AdepBridge.kt +198 -12
  21. package/templates/app-mobile/plugins/adep-bridge/android/src/main/java/com/adep/bridge/AdepIdentity.kt +342 -0
  22. package/templates/app-mobile/plugins/adep-bridge/android/src/main/java/com/adep/bridge/AdepKeystore.kt +56 -2
  23. package/templates/app-mobile/plugins/adep-bridge/android/src/main/java/com/adep/bridge/AdepSqlite.kt +506 -0
  24. package/templates/app-mobile/plugins/adep-bridge/android/src/main/java/com/adep/bridge/AdepSqliteLogic.kt +235 -0
  25. package/templates/app-mobile/plugins/adep-bridge/dist/plugin.js +30 -0
  26. package/templates/app-mobile/plugins/adep-bridge/ios/AdepBridge.podspec +3 -0
  27. package/templates/app-mobile/plugins/adep-bridge/ios/Sources/AdepBridge.swift +221 -9
  28. package/templates/app-mobile/plugins/adep-bridge/ios/Sources/AdepIdentity.swift +500 -0
  29. package/templates/app-mobile/plugins/adep-bridge/ios/Sources/AdepKeychain.swift +69 -17
  30. package/templates/app-mobile/plugins/adep-bridge/ios/Sources/AdepPermissionGate.swift +25 -4
  31. package/templates/app-mobile/plugins/adep-bridge/ios/Sources/AdepShellConfig.swift +54 -0
  32. package/templates/app-mobile/plugins/adep-bridge/ios/Sources/AdepSqlite.swift +614 -0
  33. package/templates/app-mobile/plugins/adep-bridge/package.json +10 -0
  34. package/templates/app-mobile/template.json +12 -3
  35. package/templates/app-mobile/web/adep-bridge.js +103 -9
  36. package/templates/app-mobile/web/offline-runtime.js +230 -0
  37. package/templates/app-mobile-xtool/AdepShell-Info.plist +24 -0
  38. package/templates/app-mobile-xtool/Package.swift +59 -0
  39. package/templates/app-mobile-xtool/README.md +81 -0
  40. package/templates/app-mobile-xtool/Sources/AdepShell/App.swift +36 -0
  41. package/templates/app-mobile-xtool/Sources/AdepShell/ShellViewController.swift +223 -0
  42. package/templates/app-mobile-xtool/adep.permissions.json +4 -0
  43. package/templates/app-mobile-xtool/app/site/.gitkeep +0 -0
  44. package/templates/app-mobile-xtool/template.json +15 -0
  45. package/templates/app-mobile-xtool/xtool.yml +35 -0
  46. package/templates/app-mobile/plugins/adep-bridge/capacitor.plugin.json +0 -10
@@ -5,7 +5,12 @@
5
5
  * 1. 注册自定义 scheme `adep-app://`——`file://` 直接加载 ESM bundle 会被 CORS 拦
6
6
  * (`file://` origin 为 null,ES module 跨源请求一律被拒),必须走自定义协议;
7
7
  * 2. `adep-app://bundle/<path>` 从工程内 `app/site/` 提供静态产物(带 MIME 与 SPA 回退);
8
- * 3. 创建 BrowserWindow,经 additionalArguments 把 adep.app.json 配置传给沙箱 preload。
8
+ * 3. `adep-app://bundle/api/*` 经主进程代理到平台(`/api/v1/*` 主域 / 其余项目子域)——
9
+ * 页面相对路径的函数调用落在本协议上,不经代理就是被 SPA 回退吞掉;主进程转发
10
+ * 也不受渲染进程 CORS 约束(2026-10-27 实测:不代理则在线运行时云端全断);
11
+ * 4. 创建 BrowserWindow,经 additionalArguments 把 adep.app.json 配置传给沙箱 preload;
12
+ * 5. (APP-033)首屏之后在后台做设备身份自举 → 挂同步:签发失败只留日志与状态文件,
13
+ * 绝不影响本地可用性(详见 `identity.mjs` 的三条边界)。
9
14
  *
10
15
  * 页面侧经 preload 注入的 `__ADEP_APP__` 拿到 platformUrl 后,用 `@adep/client`
11
16
  * 绝对地址 invoke 云函数(在线运行时,函数逻辑迭代不需要重新发版)。
@@ -14,11 +19,18 @@
14
19
  * 直写,比 .then() 链少一层回调;启动失败即响亮崩溃(unhandled rejection)。
15
20
  */
16
21
  import { readFileSync } from 'node:fs'
22
+ import http from 'node:http'
23
+ import https from 'node:https'
17
24
  import { dirname, join } from 'node:path'
18
25
  import { fileURLToPath, pathToFileURL } from 'node:url'
19
- import { app, ipcMain, net, protocol } from 'electron'
26
+ import { pipeline, Readable } from 'node:stream'
27
+ import { app, clipboard, dialog, ipcMain, Menu, net, protocol } from 'electron'
20
28
  import { createWindow } from './window.mjs'
21
29
  import { createOfflineRuntime, hasOfflineBundle } from './offline-runtime.mjs'
30
+ import { IdentityBootstrap, createFetchTransport, platformForHost } from './identity.mjs'
31
+ import { createProtectedStore, writeIdentityStatus } from './identity-store.mjs'
32
+ import { createRecoveryActions } from './recovery-ui.mjs'
33
+ import { createSyncLoop } from './sync-loop.mjs'
22
34
 
23
35
  const APP_SCHEME = 'adep-app'
24
36
  const SITE_DIR = 'app/site'
@@ -59,6 +71,108 @@ protocol.registerSchemesAsPrivileged([
59
71
  await app.whenReady()
60
72
  const config = loadAppConfig()
61
73
 
74
+ // ── 在线云函数 / 平台 API 代理(PRD §2.11 路径 A 的最后一环)──
75
+ //
76
+ // 页面以相对路径 fetch('/api/...') 时,相对解析落在 adep-app://bundle/api/...——协议处理器
77
+ // 必须把这类请求代理到平台,否则会被 SPA 回退吞成 index.html,页面拿不到 JSON 只能进
78
+ // 「云端不可达」的本机模式。而经渲染进程直接打 platformUrl 绝对地址也不行:跨源 + 平台
79
+ // 函数路由面无 CORS 头,浏览器层直接拒收响应(2026-10-27 countdown 导出包实测,两端全断)。
80
+ //
81
+ // 所以在主进程代理(node:http 的 connect 目标与 Host 头是分离的):
82
+ // - `/api/v1/*` → 平台自有 API,Host 用主域;
83
+ // - 其余 `/api/*` → 项目云函数,Host 用 `<slug>.<platformHost>`(网关按 Host 路由用户函数,
84
+ // 主域不路由)。连接恒打 platformUrl 的地址——Windows 不解析 `*.localhost` 通配
85
+ // (AGENTS §9 e2e ENOTFOUND 家族),直连子域会 DNS 失败。
86
+ // 主进程发请求不受渲染进程 CORS 约束,平台无需为壳 origin 开任何 CORS 面。
87
+ const PLATFORM_URL = new URL(config.platformUrl)
88
+ const PLATFORM_IS_HTTPS = PLATFORM_URL.protocol === 'https:'
89
+ const PLATFORM_TRANSPORT = PLATFORM_IS_HTTPS ? https : http
90
+ const PLATFORM_PORT = PLATFORM_URL.port || (PLATFORM_IS_HTTPS ? '443' : '80')
91
+ const PLATFORM_HOST_HEADER = PLATFORM_URL.host
92
+ // 子域形态只在「平台主机是域名」时成立(`<slug>.<host>`);platformUrl 是 IP 字面量时
93
+ // 拼出 `countdown.127.0.0.1` 这类坏 Host,被 better-auth 的 allowedHosts 直接 403
94
+ //(2026-10-27 实测)。本地开发规范域是 `localhost:<port>`(.env 五键铁律的 PLATFORM_DOMAIN
95
+ // 口径),此时回退 `<slug>.localhost:<port>`——只改 Host 头,连接仍打原地址,无需解析。
96
+ const PLATFORM_HOSTNAME_IS_IP =
97
+ /^[\d.]+$/.test(PLATFORM_URL.hostname) || PLATFORM_URL.hostname.includes(':')
98
+ const FUNCTIONS_HOST_HEADER = PLATFORM_HOSTNAME_IS_IP
99
+ ? `${config.projectSlug}.localhost${PLATFORM_URL.port ? `:${PLATFORM_URL.port}` : ''}`
100
+ : `${config.projectSlug}.${PLATFORM_URL.host}`
101
+
102
+ /**
103
+ * 把 adep-app://bundle/api/* 请求经主进程转发到平台。
104
+ *
105
+ * @param {Request} request 协议处理器收到的原始请求(method / headers / body 原样透传)
106
+ * @param {string} hostHeader 上游 Host 头(主域 = 平台 API;子域 = 项目云函数)
107
+ * @returns {Promise<Response>} 上游响应(status / headers / body 流式回传)
108
+ */
109
+ function proxyToPlatform(request, hostHeader) {
110
+ const upstreamHeaders = {}
111
+ request.headers.forEach((value, key) => {
112
+ // Host 由我们钉(决定网关路由);连接级逐跳头不透传(node:http 会自己管)。
113
+ if (key === 'host' || key === 'connection' || key === 'content-length') return
114
+ upstreamHeaders[key] = value
115
+ })
116
+ upstreamHeaders.host = hostHeader
117
+ const { pathname, search } = new URL(request.url)
118
+ return new Promise((resolveProxy) => {
119
+ // 恰好 resolve 一次:上游响应 / 中途错误 / 管道破裂三条路径都可能到达,
120
+ // 悬而未决的 Promise 会把页面 fetch 挂死(无超时、无报错)。
121
+ let settled = false
122
+ const settle = (response) => {
123
+ if (settled) return
124
+ settled = true
125
+ resolveProxy(response)
126
+ }
127
+ const upstream = PLATFORM_TRANSPORT.request(
128
+ {
129
+ host: PLATFORM_URL.hostname,
130
+ port: PLATFORM_PORT,
131
+ path: `${pathname}${search}`,
132
+ method: request.method,
133
+ headers: upstreamHeaders,
134
+ },
135
+ (res) => {
136
+ const headers = new Headers()
137
+ for (const [key, value] of Object.entries(res.headers)) {
138
+ if (value === undefined) continue
139
+ for (const item of Array.isArray(value) ? value : [value]) headers.append(key, item)
140
+ }
141
+ settle(
142
+ new Response(Readable.toWeb(res), {
143
+ status: res.statusCode ?? 502,
144
+ headers,
145
+ })
146
+ )
147
+ }
148
+ )
149
+ upstream.on('error', (error) => {
150
+ // 代理失败必须与「应用自己的 404」不同形:给 502 + 可读原因,别让页面把网络故障
151
+ // 当成业务响应,也别落进 SPA 回退(那会把 index.html 当 API 响应返回)。
152
+ settle(
153
+ new Response(
154
+ JSON.stringify({ proxyError: String(error instanceof Error ? error.message : error) }),
155
+ { status: 502, headers: { 'content-type': 'application/json' } }
156
+ )
157
+ )
158
+ })
159
+ if (request.body === null) {
160
+ upstream.end()
161
+ return
162
+ }
163
+ pipeline(Readable.fromWeb(request.body), upstream, (error) => {
164
+ if (error) {
165
+ settle(
166
+ new Response(JSON.stringify({ proxyError: String(error) }), {
167
+ status: 502,
168
+ headers: { 'content-type': 'application/json' },
169
+ })
170
+ )
171
+ }
172
+ })
173
+ })
174
+ }
175
+
62
176
  // 流式协议处理器:adep-app://bundle/<path> → app/site/<path>。
63
177
  // 用 net.fetch(pathToFileURL(...)) 而非手拼 Response:前者自动带 Content-Type
64
178
  //(ESM 对 MIME 严格校验,手写 Response 不给 MIME 会被静默拒载)。
@@ -67,6 +181,13 @@ protocol.handle(APP_SCHEME, async (request) => {
67
181
  if (host !== 'bundle') {
68
182
  return new Response(`未知 host "${host}"`, { status: 404 })
69
183
  }
184
+ // /api/* 不落静态面:页面相对路径的云函数 / 平台 API 调用经主进程代理(见上方注释)。
185
+ if (pathname === '/api' || pathname.startsWith('/api/')) {
186
+ return proxyToPlatform(
187
+ request,
188
+ pathname.startsWith('/api/v1/') ? PLATFORM_HOST_HEADER : FUNCTIONS_HOST_HEADER
189
+ )
190
+ }
70
191
  const siteRoot = join(APP_ROOT, SITE_DIR)
71
192
  const relative = decodeURIComponent(pathname).replace(/^\/+/, '')
72
193
  const rootUrl = pathToFileURL(siteRoot).href
@@ -97,6 +218,101 @@ if (hasOfflineBundle()) {
97
218
  const win = await createWindow(config, { offline: offline !== null })
98
219
  await win.loadURL(`${APP_SCHEME}://bundle/index.html`)
99
220
 
221
+ /** 身份 / 同步日志:GUI 形态下用户看不到 stdout,所以另有一份状态文件给 doctor 读。 */
222
+ function logLine(line) {
223
+ console.log(line)
224
+ }
225
+
226
+ /**
227
+ * 受保护存储 + 恢复码动作(APP-034)。
228
+ *
229
+ * store 提到模块级:自举与恢复码读口共用同一实例(一个后端、一份降级状态)。
230
+ * 恢复码的呈现与导出全在主进程(应用菜单 + 原生对话框)——**不开 IPC、不改 preload**,
231
+ * 恢复码与 ownerKey 都不进渲染进程(APP-033 的边界不回退)。
232
+ */
233
+ const identityStore = createProtectedStore({
234
+ root: APP_ROOT,
235
+ // 凭据条目的 service 串:一眼能看出属于哪个平台项目,且不含任何机密。
236
+ service: `adep-app:${config.projectSlug}`,
237
+ log: logLine,
238
+ })
239
+ const recoveryActions = createRecoveryActions({
240
+ root: APP_ROOT,
241
+ readRecoveryCode: () => identityStore.readRecoveryCode(),
242
+ appName: config.appName,
243
+ projectSlug: config.projectSlug,
244
+ deps: { dialog, clipboard, log: logLine },
245
+ })
246
+
247
+ // 应用菜单挂「凭据」子菜单(用户的设置/关于面——壳没有自己的设置页,页面是用户内容)。
248
+ // macOS 保留系统惯例角色(appMenu / editMenu…),否则复制粘贴快捷键全失效。
249
+ Menu.setApplicationMenu(
250
+ Menu.buildFromTemplate([
251
+ ...(process.platform === 'darwin' ? [{ role: 'appMenu' }] : []),
252
+ { role: 'fileMenu' },
253
+ { role: 'editMenu' },
254
+ { role: 'viewMenu' },
255
+ { role: 'windowMenu' },
256
+ { label: '凭据', submenu: recoveryActions.menuItems() },
257
+ ])
258
+ )
259
+
260
+ /**
261
+ * 设备身份自举 + 后台同步(APP-033)。
262
+ *
263
+ * **刻意排在首屏之后**(`void`,不 await):首启要发一次真实网络请求,断网时最长要等到
264
+ * 超时;身份只影响同步与(未来的)库 key,不影响页面加载——为了它让用户盯着白窗口是错的。
265
+ * 失败必须只留日志、不改变可用性(`IdentityBootstrap.start()` 永不 throw)。
266
+ *
267
+ * 身份就绪后再 `attachSync`:本地库早就打开(离线运行时装配时),此处只挂 transport,
268
+ * 不重开第二个 `DatabaseSync` 句柄(两个句柄写同一个 SQLite 文件=拿锁打架)。
269
+ */
270
+ async function bootstrapDeviceIdentity() {
271
+ const bootstrap = new IdentityBootstrap({
272
+ readConfig: () => JSON.parse(readFileSync(join(APP_ROOT, 'adep.app.json'), 'utf8')),
273
+ // 与恢复码动作共用同一份受保护存储(见文件上方 APP-034 注释)。
274
+ store: identityStore,
275
+ transport: createFetchTransport(10_000),
276
+ // 宿主 OS → 服务端 APP_PLATFORMS(没有 `desktop` 这个取值,见 identity.mjs 注释)。
277
+ platform: platformForHost(process.platform),
278
+ log: logLine,
279
+ // 读数面:桌面壳是 GUI,stdout 用户看不到 ⇒ 状态另落一份给 `adep app doctor` 读。
280
+ recordStatus: (record) => writeIdentityStatus(APP_ROOT, record),
281
+ })
282
+
283
+ const outcome = await bootstrap.start()
284
+ if (outcome.status === 'failed') {
285
+ logLine(
286
+ `[adep-app] 身份未就绪(${outcome.code}):${outcome.detail ?? ''}——` +
287
+ '应用照常可用,下次启动重试;排查用 adep app doctor'
288
+ )
289
+ return
290
+ }
291
+
292
+ // 恢复码首次呈现(APP-034):码可读且没呈现过 ⇒ 弹一次。刻意不绑「本次刚签发」——
293
+ // 老安装升级上来时用户同样一次没见过码;已呈现过 ⇒ 幂等空操作。失败只留日志。
294
+ void recoveryActions.showOnceIfNeeded().catch((error) => {
295
+ logLine(`[adep-app] 恢复码呈现失败(不影响其他功能):${error?.message ?? error}`)
296
+ })
297
+
298
+ if (offline === null) {
299
+ // 在线形态没有本地库,同步没有载体(不是错误:在线运行时函数本来就在服务端跑)。
300
+ logLine('[adep-app] 在线形态:不做本地同步')
301
+ return
302
+ }
303
+
304
+ const sync = offline.attachSync({ ownerKey: outcome.ownerKey })
305
+ const loop = createSyncLoop({ sync, log: logLine })
306
+ loop.start()
307
+ // 退出前停循环:定时器在 quit 之后还会 fire,那时 sqlite 句柄可能已关。
308
+ app.on('before-quit', () => loop.stop())
309
+ }
310
+
311
+ void bootstrapDeviceIdentity().catch((error) => {
312
+ // 兜底:这里的任何异常都只该是「同步没起来」,绝不能让未处理的 rejection 崩主进程。
313
+ console.error('[adep-app] 身份自举异常(不影响本地可用性):', error)
314
+ })
315
+
100
316
  app.on('activate', () => {
101
317
  // macOS:点 Dock 图标且无窗口时重建(Electron 官方生命周期约定)。
102
318
  if (app.getAllWindows().length === 0) void createWindow(config)
@@ -8,7 +8,7 @@
8
8
  * → 造 DeviceBridge(node:sqlite + 只读文件桥 + 空 env——离线包永不下发 secret)
9
9
  * → executor.createLocalExecutor(白名单路由:offline:true 本地执行,false 返回
10
10
  * FN_OFFLINE_ONLY 且提示在线调用方式)
11
- * → 返回 { executor, manifest, sqlite, version } 供主进程 ipc 面使用。
11
+ * → 返回 { executor, manifest, sqlite, version }(给 ownerKey 时另带 sync)供主进程 ipc 面使用。
12
12
  *
13
13
  * 壳工程是**独立 npm 工程**(装不了私有 workspace 包),运行时随包分发是本模板对
14
14
  * 「接 @adep/app-runtime」的唯一可行落地;e2e 黄金测试(e2e/cli/app-offline.spec.ts)
@@ -47,8 +47,12 @@ function readFunctionSource(name) {
47
47
  /**
48
48
  * 装配离线运行时:装载 executor(data URL 动态 import,引擎无关标准 ESM 语义)、
49
49
  * 初始化本地库、造桥。任何一步失败即 throw(壳启动即响亮崩溃,不留半离线坏状态)。
50
+ *
51
+ * `options.ownerKey`(APP-032):设备身份。给了就**额外**装配同步 transport
52
+ * (`push` / `pull`),用的是随包分发的同一份设备端代码(bundle 导出 `createSyncTransport`),
53
+ * 与 `adep app sync` 逐字同源。不给就不装——同步没有身份就没有分母,**不**做匿名上行。
50
54
  */
51
- export async function createOfflineRuntime() {
55
+ export async function createOfflineRuntime(options = {}) {
52
56
  if (!hasOfflineBundle()) {
53
57
  throw new Error('[adep-app] 未检测到离线产物 app/offline/manifest.json:请先执行 adep app build --offline')
54
58
  }
@@ -125,5 +129,70 @@ export async function createOfflineRuntime() {
125
129
  const local = executorModule.createLocalExecutor({ functions, bridge, env })
126
130
  const version = readFileSync(join(OFFLINE_DIR, 'runtime', 'version.txt'), 'utf8').trim()
127
131
 
128
- return { executor: local, manifest, sqlite, version }
132
+ // 同步(APP-032/APP-033):设备端 transport 是**随包分发的那一份**,不是这里另写一套。
133
+ //
134
+ // 装配入口拆成 `attachSync({ ownerKey })` 而不是只在构造参数里给:桌面壳的身份是**异步**
135
+ // 自举的(首启要发一次网络请求,APP-033)——窗口先出来、身份后到,此时运行时与本地库
136
+ // 早就打开了。若只保留「构造时传 ownerKey」,主进程要么阻塞首屏等签名,要么重新打开
137
+ // 一次库(第二个 DatabaseSync 句柄同时写一个文件=拿锁打架)。
138
+ const createSync = (syncOwnerKey, fetchOverride) => {
139
+ if (typeof executorModule.createSyncTransport !== 'function') {
140
+ throw new Error(
141
+ '[adep-app] 包内运行时不含同步 transport(runtime/executor.mjs 是旧版本):请重跑 adep app build --offline'
142
+ )
143
+ }
144
+ // 同步目标取壳配置(与页面 / 主进程同一份 adep.app.json),不另立一份环境变量。
145
+ const appConfig = JSON.parse(readFileSync(join(APP_ROOT, 'adep.app.json'), 'utf8'))
146
+ if (typeof appConfig.platformUrl !== 'string' || typeof appConfig.projectSlug !== 'string') {
147
+ throw new Error('[adep-app] adep.app.json 缺 platformUrl / projectSlug:同步端点路径拼不出来')
148
+ }
149
+ const driver = executorModule.createDeviceSqliteDriver(sqliteBridge)
150
+ return executorModule.createSyncTransport({
151
+ driver,
152
+ ownerKey: syncOwnerKey,
153
+ baseUrl: appConfig.platformUrl,
154
+ projectSlug: appConfig.projectSlug,
155
+ // 渲染进程有 window.fetch,主进程 / Node 22 有全局 fetch:两者都满足同一注入面。
156
+ fetch: fetchOverride ?? options.fetch ?? ((url, init) => fetch(url, init)),
157
+ })
158
+ }
159
+
160
+ /**
161
+ * 身份就绪后再挂同步(APP-033)。`ownerKey` 为空即**不挂**(不做匿名上行)。
162
+ *
163
+ * 重复调用返回同一个实例:主进程可能同时被「自举完成」和「窗口重建」两条路径调到。
164
+ */
165
+ let attachedSync
166
+ const attachSync = ({ ownerKey, fetch: overrideFetch } = {}) => {
167
+ const key = ownerKey === undefined || ownerKey === '' ? options.ownerKey : ownerKey
168
+ if (key === undefined || key === '') {
169
+ throw new Error('[adep-app] attachSync 需要设备身份 ownerKey(无身份不做匿名同步)')
170
+ }
171
+ if (attachedSync !== undefined) return attachedSync
172
+ attachedSync = createSync(key, overrideFetch)
173
+ return attachedSync
174
+ }
175
+
176
+ const sync =
177
+ options.ownerKey === undefined || options.ownerKey === ''
178
+ ? undefined
179
+ : createSync(options.ownerKey)
180
+
181
+ const close = () => {
182
+ // DatabaseSync.close() 释放文件句柄;Windows 上不显式 close 时文件锁靠 GC 终结器
183
+ // 释放,与 afterAll/退出时的 rm/卸载赛跑(e2e 实测 EBUSY,POSIX 因锁语义宽松恒绿)。
184
+ if (sqlite !== undefined) {
185
+ sqlite.close()
186
+ }
187
+ }
188
+
189
+ return {
190
+ executor: local,
191
+ manifest,
192
+ sqlite,
193
+ version,
194
+ attachSync,
195
+ close,
196
+ ...(sync === undefined ? {} : { sync }),
197
+ }
129
198
  }
@@ -3,7 +3,7 @@
3
3
  "version": "0.1.0",
4
4
  "private": true,
5
5
  "description": "{{appName}} —— AgentDeploy 桌面应用(Electron 壳,在线 + 离线运行时)",
6
- "main": "main.mjs",
6
+ "main": "main.cjs",
7
7
  "scripts": {
8
8
  "start": "electron .",
9
9
  "dist": "electron-builder"
@@ -20,13 +20,15 @@
20
20
  "output": "dist"
21
21
  },
22
22
  "files": [
23
- "main.mjs",
24
- "window.mjs",
23
+ "*.mjs",
24
+ "*.cjs",
25
25
  "preload.cjs",
26
26
  "adep.app.json",
27
27
  "app/site/**/*",
28
28
  "app/offline/**/*",
29
- "data/**/*"
29
+ "data/**/*",
30
+ "!data/identity/**",
31
+ "!data/*.sqlite*"
30
32
  ],
31
33
  "mac": {
32
34
  "target": [
@@ -36,12 +38,14 @@
36
38
  },
37
39
  "win": {
38
40
  "target": [
39
- "nsis"
41
+ "nsis",
42
+ "zip"
40
43
  ]
41
44
  },
42
45
  "linux": {
43
46
  "target": [
44
- "AppImage"
47
+ "AppImage",
48
+ "deb"
45
49
  ],
46
50
  "category": "Utility"
47
51
  }