@adep/cli 0.4.4 → 0.4.5

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 +33409 -27038
  3. package/dist/vite.js +209 -15
  4. package/dist/worker-entry.js +163 -1
  5. package/package.json +5 -4
  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
@@ -0,0 +1,223 @@
1
+ import AdepBridge
2
+ import Capacitor
3
+ import Foundation
4
+
5
+ /// 包内站点位置。
6
+ ///
7
+ /// `xtool.yml` 声明 `resources: [app/site]`,Packer 的 `packFileToRoot` 走
8
+ /// `FileManager.copyItem(at:to:)`(对目录递归复制),落点 `<.app>/<末段目录名>`
9
+ /// ⇒ 包内路径恒为 `Bundle.main.bundleURL/site/`,与仓库里的 `app/site/` 一一对应。
10
+ ///
11
+ /// ⚠️ **不要**改成 `public/`(Capacitor 的默认 `appLocation`)来"省掉"这次重定向:
12
+ /// `public/` 是 `cap sync` 的产物目录名,与 CLI 注入契约(`app/site/`)不是同一个概念;
13
+ /// 两处都用 `public/` 会在同一包里出现两种来源的同一个目录名。
14
+ enum ShellSite {
15
+ static let directoryName = "site"
16
+ static let entryName = "index.html"
17
+
18
+ static var packagedDirectory: URL {
19
+ Bundle.main.bundleURL.appendingPathComponent(directoryName, isDirectory: true)
20
+ }
21
+
22
+ static var packagedEntry: URL {
23
+ packagedDirectory.appendingPathComponent(entryName, isDirectory: false)
24
+ }
25
+
26
+ /// 站点是否随包注入(判 `index.html` 是**文件**,不是只看目录存在——
27
+ /// 空目录 `site/` 会让 Capacitor 的 `appStartFileURL` 检查走到 `fatalLoadError()` 直接崩)。
28
+ static func isInstalled(in fileManager: FileManager = .default) -> Bool {
29
+ var isDirectory: ObjCBool = false
30
+ let exists = fileManager.fileExists(atPath: packagedEntry.path, isDirectory: &isDirectory)
31
+ return exists && !isDirectory.boolValue
32
+ }
33
+ }
34
+
35
+ /// 站点缺失时的**看得见的**错误页。
36
+ ///
37
+ /// 为什么必须有:Capacitor 的 `loadWebView()` 在 `appStartFileURL` 不存在时调
38
+ /// `fatalLoadError()` → `fatalError`(**进程崩溃**,源码 `CAPBridgeViewController.swift`)。
39
+ /// 而"白屏"与"崩溃"都是最难定位的两类症状;这里换成一个写明原因的错误页,
40
+ /// 让「CLI 没注入站点」这件事在设备上一眼可见(fail loud 且不 crash)。
41
+ enum ShellFallbackPage {
42
+ private static let directoryName = "adep-shell-missing-site"
43
+
44
+ /// 生成错误页目录并返回;失败返回 nil(调用方回退到 Capacitor 原生报错路径)。
45
+ static func materialize(message: String, in fileManager: FileManager = .default) -> URL? {
46
+ let directory = fileManager.temporaryDirectory
47
+ .appendingPathComponent(directoryName, isDirectory: true)
48
+ do {
49
+ try fileManager.createDirectory(at: directory, withIntermediateDirectories: true)
50
+ let entry = directory.appendingPathComponent(ShellSite.entryName, isDirectory: false)
51
+ try html(message: message).write(to: entry, atomically: true, encoding: .utf8)
52
+ return directory
53
+ } catch {
54
+ NSLog("[adep-shell] 错误页写入失败:\(error)")
55
+ return nil
56
+ }
57
+ }
58
+
59
+ private static func html(message: String) -> String {
60
+ let escaped = message
61
+ .replacingOccurrences(of: "&", with: "&amp;")
62
+ .replacingOccurrences(of: "<", with: "&lt;")
63
+ .replacingOccurrences(of: ">", with: "&gt;")
64
+ return """
65
+ <!doctype html>
66
+ <html lang="zh-CN">
67
+ <head>
68
+ <meta charset="utf-8" />
69
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
70
+ <title>AdepShell</title>
71
+ <style>
72
+ body { margin: 0; padding: 24px; font: 15px/1.6 -apple-system, system-ui, sans-serif; }
73
+ h1 { font-size: 17px; margin: 0 0 12px; }
74
+ code { font-size: 13px; word-break: break-all; }
75
+ </style>
76
+ </head>
77
+ <body>
78
+ <h1>站点资源未就绪</h1>
79
+ <p>页面内容应随包注入到 <code>site/</code>,但当前包内缺失:</p>
80
+ <p><code>\(escaped)</code></p>
81
+ <p>这是构建期问题(CLI 未注入前端产物),不是网络问题。</p>
82
+ </body>
83
+ </html>
84
+ """
85
+ }
86
+ }
87
+
88
+ /// 页面侧诊断注入(真机排障通用面,APP-022 白屏取证落地)。
89
+ ///
90
+ /// 页面里的 console / 未捕获异常 / 未处理 rejection 在 WKWebView 里默认**不可见**
91
+ /// (Safari 远程检查要接 Mac)。把三类事件全部经 `WKScriptMessageHandler` 转发到
92
+ /// 系统日志(前缀 `[adep-web]`),真机日志里直接可见页面发生了什么,不用接 Mac。
93
+ /// 诊断脚本经覆写 `webViewConfiguration(for:)` 注入——`prepareWebView` 消费的就是它,
94
+ /// 注入时点早于任何页面脚本(`loadWebView()` 在 `viewDidLoad()` 里,晚于 `loadView()`)。
95
+ enum PageDiagnostics {
96
+ static let source = """
97
+ ;(function(){
98
+ var send = function(level, msg) {
99
+ try { window.webkit.messageHandlers.adepDiag.postMessage(level + ': ' + msg); } catch (e) {}
100
+ };
101
+ ['log','warn','error','info'].forEach(function(level){
102
+ var orig = console[level] ? console[level].bind(console) : function(){};
103
+ console[level] = function(){ send(level, Array.prototype.map.call(arguments, String).join(' ')); orig.apply(null, arguments); };
104
+ });
105
+ window.addEventListener('error', function(e){ send('pageerror', e.message + ' @' + e.filename + ':' + e.lineno); });
106
+ window.addEventListener('unhandledrejection', function(e){ send('rejection', String(e.reason)); });
107
+ })();
108
+ """
109
+ }
110
+
111
+ /// 加载回执探针:页面加载完成后延迟读取页面真实状态打系统日志。
112
+ ///
113
+ /// 为什么要有:`didFinishNavigation` 只证明「导航完成」,不证明「页面活着」——
114
+ /// JS 全灭的页面同样会走完 didFinish。1.5s / 4s 两条回执读 `document.body.innerText`
115
+ /// 与 `window.__ADEP_APP__`(页面桥装配结果),把「页面真实状态」变成日志可判读的读数。
116
+ ///
117
+ /// ⚠️ 装配失败时**必须把原因也打出来**(APP-030):页面桥失败后会落一个 `mode: 'online'`
118
+ /// 的态对象,光看 `adepApp=online/-` 与正常在线装配**一模一样**——读数存在却区分不了
119
+ /// 「装备好了」和「配置没进包」。三个错误字段(configError / offlineError / timeoutError)
120
+ /// 因此一并进回执。
121
+ enum LoadReceipt {
122
+ static let source = """
123
+ ;(function(){
124
+ var report = function(tag){
125
+ var app = window.__ADEP_APP__;
126
+ var err = !app ? '' : (app.configError ? ' CONFIG_ERR=' + app.configError.slice(0, 160) : '')
127
+ + (app.offlineError ? ' OFFLINE_ERR=' + app.offlineError.slice(0, 160) : '')
128
+ + (app.timeoutError ? ' TIMEOUT_ERR=' + app.timeoutError.slice(0, 160) : '');
129
+ return tag + ' bodyText=' + (document.body ? document.body.innerText.slice(0, 80) : 'null')
130
+ + ' adepApp=' + (app ? app.mode + '/' + (app.version || '-') + err : 'MISSING');
131
+ };
132
+ setTimeout(function(){ try { window.webkit.messageHandlers.adepDiag.postMessage('RECEIPT-1.5s ' + report('t1.5')); } catch(e){} }, 1500);
133
+ setTimeout(function(){ try { window.webkit.messageHandlers.adepDiag.postMessage('RECEIPT-4s ' + report('t4') + ' url=' + location.href); } catch(e){} }, 4000);
134
+ })();
135
+ """
136
+ }
137
+
138
+ /// 诊断消息的宿主(`adepDiag` handler 的接收端;只做转发打日志)。
139
+ ///
140
+ /// ⚠️ **必须用 `NSLog`,不能用 `CAPLog.print`**:`CAPLog.print` 内部是 `Swift.print`
141
+ /// → 写 **stdout**;SpringBoard 拉起的 app 没人接 stdout,输出直接蒸发。
142
+ /// 而 `NSLog` 走 os_log,`idevicesyslog` 才收得到(2026-10-22 真机实测:
143
+ /// syslog 里只有 WebKit 自己的 os_log 行,全部 `[adep-shell]`/`[adep-web]` 行缺失)。
144
+ final class DiagMessageHandler: NSObject, WKScriptMessageHandler {
145
+ func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
146
+ NSLog("[adep-web] \(message.body)")
147
+ }
148
+ }
149
+
150
+ /// 壳的根视图控制器:Capacitor 的 WebView 宿主。
151
+ ///
152
+ /// 站点注入点只有一个——覆写 `instanceDescriptor()` 把 `appLocation` 指到包内 `site/`。
153
+ /// 之后 Capacitor 自己会:
154
+ /// 1. `assetHandler.setAssetPath(appLocation.path)`(`loadView()` 里);
155
+ /// 2. `loadWebView()` 检查 `appStartFileURL` = `appLocation/index.html` 存在;
156
+ /// 3. 从 `appStartServerURL` 加载——即 `capacitor://localhost/`,由 Capacitor 的
157
+ /// `WKURLSchemeHandler` 从 `appLocation` 供文件。
158
+ ///
159
+ /// ⚠️ **刻意不用 `loadFileURL(_:allowingReadAccessTo:)`**(任务单原设想的形态):
160
+ /// file:// 页面的 origin 是 **null**,`fetch()` 打平台地址(`platformUrl`,https)会带
161
+ /// `Origin: null`,服务端要额外放行 null origin;而走 Capacitor 的本地 scheme 拿到的是
162
+ /// 正常源 `capacitor://localhost`,且 MIME / 相对路径解析由框架既有实现负责。
163
+ /// 这条设计改动已回填计划 §12.20(N-2 的验收形态随之变化:不再依赖 file 读取权限语义)。
164
+ final class ShellViewController: CAPBridgeViewController {
165
+ /// 把 `AdepBridge` 交给 Capacitor 的插件表(APP-018)。
166
+ ///
167
+ /// **时机依据(Capacitor 7.6.9 源码)**:`loadView()` 末尾调 `capacitorDidLoad()`,而
168
+ /// `loadWebView()` 在 `viewDidLoad()` 里 ⇒ 注册早于页面加载与 JS 导出;`registerPluginInstance`
169
+ /// 内部还会 `pluginInstance.load(on:)` + `JSExport.exportJS(...)`,所以插件脚本一定先于页面就绪。
170
+ ///
171
+ /// ⚠️ **不要改成 `registerPluginType(_:)`**:它首行是 `if autoRegisterPlugins { return }`,
172
+ /// 而 `CAPBridgeViewController` 构造 bridge 时没传该开关(默认 true)⇒ 类型注册是**空操作**,
173
+ /// 写了等于没写、且不报错(`CapacitorBridge.swift`)。实例注册不受该开关影响,并且写进
174
+ /// 的正是 `plugin(withName:)` 读的那张 `plugins` 表。
175
+ override func capacitorDidLoad() {
176
+ super.capacitorDidLoad()
177
+ guard let bridge = bridge else {
178
+ NSLog("[adep-shell] capacitorDidLoad 时 bridge 为 nil —— AdepBridge 未注册")
179
+ return
180
+ }
181
+ bridge.registerPluginInstance(AdepBridge())
182
+ // 正向证据(不是「应该注册了」):注册失败时 Capacitor 只打一行日志就 return
183
+ // (`as? CapacitorPlugin` 不满足 / 实例为 nil),而页面上的症状与「没注册」完全一样
184
+ // ——都是 NATIVE_UNAVAILABLE。所以这里必须留一行可读的判活读数。
185
+ let registered = bridge.plugin(withName: "AdepBridge") != nil
186
+ // NSLog 而非 CAPLog.print——stdout 在 SpringBoard 拉起的 app 里无人接收(见 DiagMessageHandler)。
187
+ NSLog("[adep-shell] AdepBridge registered: \(registered)")
188
+ }
189
+
190
+ /// 注入页面诊断脚本(console / error / rejection → 系统日志)+ 加载回执探针。
191
+ /// 依据见 `PageDiagnostics` 注释;handler 加在 super 的 controller 上(保留 Capacitor 自身注入)。
192
+ override func webViewConfiguration(for instanceConfiguration: InstanceConfiguration) -> WKWebViewConfiguration {
193
+ let config = super.webViewConfiguration(for: instanceConfiguration)
194
+ let controller = config.userContentController
195
+ controller.add(DiagMessageHandler(), name: "adepDiag")
196
+ controller.addUserScript(WKUserScript(source: PageDiagnostics.source, injectionTime: .atDocumentStart, forMainFrameOnly: true))
197
+ controller.addUserScript(WKUserScript(source: LoadReceipt.source, injectionTime: .atDocumentEnd, forMainFrameOnly: true))
198
+ return config
199
+ }
200
+
201
+ override func instanceDescriptor() -> InstanceDescriptor {
202
+ let descriptor = super.instanceDescriptor()
203
+ descriptor.appLocation = resolveAppLocation()
204
+ // 装载读数(白屏排障,APP-022):appLocation 解析结果是壳层唯一的自由变量,
205
+ // 打出来才能区分「包内 site 缺失走了错误页」与「正常指向 site/」两种形态。
206
+ // NSLog 而非 CAPLog.print——stdout 在 SpringBoard 拉起的 app 里无人接收。
207
+ NSLog("[adep-shell] appLocation: \(descriptor.appLocation.path)")
208
+ return descriptor
209
+ }
210
+
211
+ private func resolveAppLocation() -> URL {
212
+ if ShellSite.isInstalled() {
213
+ return ShellSite.packagedDirectory
214
+ }
215
+ let expected = ShellSite.packagedEntry.path
216
+ if let fallback = ShellFallbackPage.materialize(message: expected) {
217
+ NSLog("[adep-shell] app/site 未注入,改用错误页;期望路径:\(expected)")
218
+ return fallback
219
+ }
220
+ // 兜底:交回原路径,让 Capacitor 的 fatalLoadError 把真实期望路径打出来(fail loud)。
221
+ return ShellSite.packagedDirectory
222
+ }
223
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "permissions": []
4
+ }
File without changes
@@ -0,0 +1,15 @@
1
+ {
2
+ "code": "app-mobile-xtool",
3
+ "name": "移动应用壳(xtool / iOS 原生)",
4
+ "description": "xtool 壳工程:SwiftPM app target(AdepShell)+ WKWebView 消费 app/site/ 产物,经 xtool 编译为**真 iOS app**(不依赖 Xcode 工程文件);在线运行时(云函数经 platformUrl 服务端执行);原生桥仍复用 plugins/adep-bridge(源码单一来源,渲染期注入)",
5
+ "version": "0.1.0",
6
+ "variables": [
7
+ {
8
+ "name": "projectSlug",
9
+ "description": "平台项目 slug(写入包名 / adep.app.json / bundle id 推导)"
10
+ },
11
+ { "name": "appName", "description": "应用显示名(写 Info.plist 的 CFBundleDisplayName;产品名固定 AdepShell,见 README)" },
12
+ { "name": "platformUrl", "description": "平台地址(页面侧 @adep/client 以此为 baseUrl invoke 云函数)" },
13
+ { "name": "iosBundleId", "description": "iOS bundle identifier(top.adep.<slug>,连字符合法)" }
14
+ ]
15
+ }
@@ -0,0 +1,35 @@
1
+ # xtool 壳工程配置(APP-016)。
2
+ #
3
+ # ⚠️ **键名必须逐字来自 xtool 的 PackSchemaBase**(源码 `Sources/PackLib/PackSchema.swift`):
4
+ # version / orgID / bundleID / product / infoPath / entitlementsPath / iconPath /
5
+ # resources / extensions / skipLSP
6
+ # Yams 解码**静默忽略未知键** ⇒ 拼错(如小写 `bundleId`)不报错、只是不生效,
7
+ # 而症状是「bundle id 还是默认值」这种极难定位的偏差。`app-shell-xtool.test.ts` 的键白名单
8
+ # 就是钉这一条。
9
+ #
10
+ # 各键在此处的取值理由:
11
+ # - `bundleID`:渲染期由 CLI 填 `top.adep.<slug>`(免费签名时 `com.example.*` 会被 Apple 拒,
12
+ # 计划 §12.16 踩过)。留成模板变量,不写死。
13
+ # ⚠️ 值**必须加引号**:YAML 1.2 把不带引号的双花括号占位符解析成 **flow mapping**
14
+ # (花括号套花括号)而不是字符串。渲染器在文本层替换,所以产物碰巧仍是合法 YAML,
15
+ # 但模板文件本身对任何 YAML 工具都是坏结构——实测就是这个坑(首版未加引号,
16
+ # 测试里的 `parse()` 读出来是 `{ "{ iosBundleId }": null }`)。
17
+ # ⚠️ 注释里也**不得**出现「双花括号 + 英文标识符」的字面量:渲染器会把它当变量、
18
+ # 白名单校验直接 fail loud(实测:注释里写了单个字母的示范串 ⇒ adep app init 当场抛
19
+ # SHELL_TEMPLATE_INVALID)。所以坑只能这样用文字描述,不能写出样子来
20
+ # (与「JSDoc 里不能写 cron 通配」同族:被解析的文本里不能放会被解析的样例)。
21
+ # - `product`:必须与 Package.swift 的 library product 名逐字一致(`AdepShell`)——
22
+ # xtool 按名字在包内选主 product,对不上报「找不到 main product」。
23
+ # - `infoPath`:只放**要覆盖**的键。xtool 已经默认注入 CFBundleIdentifier / CFBundleVersion /
24
+ # MinimumOSVersion(取 Package.swift 的 iOS 版本)/ UILaunchScreen / 屏幕方向等,
25
+ # 这里的 plist 是 `merge(uniquingKeysWith: { $1 })` ⇒ 同名键以本文件为准。
26
+ # - `resources`:**目录是被支持的**(不是只吃单文件)。Packer 的 `packFileToRoot` 走
27
+ # `FileManager.copyItem(at:to:)`——对目录递归复制,落点是 `<.app>/<lastPathComponent>`
28
+ # ⇒ 写 `app/site` 得到 `Payload/AdepShell.app/site/`,嵌套相对路径原样保留。
29
+ # ⚠️ 各条资源的**末段目录名**不能撞车(copyItem 目标已存在即抛错)。
30
+ version: 1
31
+ bundleID: "{{iosBundleId}}"
32
+ product: AdepShell
33
+ infoPath: AdepShell-Info.plist
34
+ resources:
35
+ - app/site
@@ -1,10 +0,0 @@
1
- {
2
- "ios": {
3
- "src": "ios",
4
- "dest": "ios"
5
- },
6
- "android": {
7
- "src": "android",
8
- "dest": "android"
9
- }
10
- }