@marktrue/dsh-mobile-remote 3.2.1-alpha.3 → 3.2.2-alpha.1-app

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 (349) hide show
  1. package/DSH-Remote-v3.2.2.apk +4 -0
  2. package/README.md +6 -253
  3. package/manifest.json +21 -0
  4. package/package.json +12 -65
  5. package/CHANGELOG.md +0 -810
  6. package/CONTRIBUTING.md +0 -59
  7. package/FAQ.md +0 -111
  8. package/cordis.patch.yml +0 -14
  9. package/docs/00-/345/274/200/345/217/221/346/200/273/347/272/262.md +0 -122
  10. package/docs/01-PRD.md +0 -148
  11. package/docs/02-architecture.md +0 -249
  12. package/docs/03-api.md +0 -816
  13. package/docs/04-security.md +0 -96
  14. package/docs/05-test-cases.md +0 -326
  15. package/docs/06-install-run.md +0 -308
  16. package/docs/07-user-manual.md +0 -65
  17. package/docs/08-/346/211/251/345/261/225/345/274/200/345/217/221/346/214/207/345/215/227.md +0 -86
  18. package/docs/09-compatibility.md +0 -195
  19. package/docs/10-release.md +0 -927
  20. package/docs/screenshots/01-home.png +0 -0
  21. package/docs/screenshots/02-notifications.png +0 -0
  22. package/docs/screenshots/03-sessions.png +0 -0
  23. package/docs/screenshots/04-chat.png +0 -0
  24. package/docs/screenshots/05-settings.png +0 -0
  25. package/docs/screenshots/06-new-session.png +0 -0
  26. package/docs/screenshots/pgy-appstore.jpg +0 -0
  27. package/docs/screenshots/pgy-mobile-app.jpg +0 -0
  28. package/docs/screenshots/pgy-pc-client.jpg +0 -0
  29. package/lib/account-usage.js +0 -145
  30. package/lib/app-alignment.js +0 -112
  31. package/lib/app-carrier-store.js +0 -1972
  32. package/lib/app-carrier.js +0 -602
  33. package/lib/client.js +0 -706
  34. package/lib/desktop-form.js +0 -738
  35. package/lib/endpoint-registry.js +0 -162
  36. package/lib/git-read-bundled-provider.js +0 -255
  37. package/lib/git-read-service.js +0 -322
  38. package/lib/index.js +0 -9292
  39. package/lib/plugin-content-digest.js +0 -80
  40. package/lib/plugin-self-update.js +0 -758
  41. package/lib/plugin-update-controller.js +0 -95
  42. package/lib/plugin-update-transaction.js +0 -322
  43. package/lib/plugin-update.js +0 -309
  44. package/lib/runtime-dependency-snapshot.js +0 -345
  45. package/lib/session-title-cache.js +0 -123
  46. package/lib/session-title-refresh.js +0 -261
  47. package/lib/update-fetch.js +0 -24
  48. package/lib/update-lock-arbiter.js +0 -158
  49. package/lib/update-supervisor.js +0 -312
  50. package/node_modules/ansi-regex/index.d.ts +0 -37
  51. package/node_modules/ansi-regex/index.js +0 -10
  52. package/node_modules/ansi-regex/license +0 -9
  53. package/node_modules/ansi-regex/package.json +0 -55
  54. package/node_modules/ansi-regex/readme.md +0 -78
  55. package/node_modules/ansi-styles/index.d.ts +0 -345
  56. package/node_modules/ansi-styles/index.js +0 -163
  57. package/node_modules/ansi-styles/license +0 -9
  58. package/node_modules/ansi-styles/package.json +0 -56
  59. package/node_modules/ansi-styles/readme.md +0 -152
  60. package/node_modules/camelcase/index.d.ts +0 -63
  61. package/node_modules/camelcase/index.js +0 -76
  62. package/node_modules/camelcase/license +0 -9
  63. package/node_modules/camelcase/package.json +0 -43
  64. package/node_modules/camelcase/readme.md +0 -99
  65. package/node_modules/cliui/CHANGELOG.md +0 -76
  66. package/node_modules/cliui/LICENSE.txt +0 -14
  67. package/node_modules/cliui/README.md +0 -115
  68. package/node_modules/cliui/index.js +0 -354
  69. package/node_modules/cliui/package.json +0 -65
  70. package/node_modules/color-convert/CHANGELOG.md +0 -54
  71. package/node_modules/color-convert/LICENSE +0 -21
  72. package/node_modules/color-convert/README.md +0 -68
  73. package/node_modules/color-convert/conversions.js +0 -839
  74. package/node_modules/color-convert/index.js +0 -81
  75. package/node_modules/color-convert/package.json +0 -48
  76. package/node_modules/color-convert/route.js +0 -97
  77. package/node_modules/color-name/LICENSE +0 -8
  78. package/node_modules/color-name/README.md +0 -11
  79. package/node_modules/color-name/index.js +0 -152
  80. package/node_modules/color-name/package.json +0 -28
  81. package/node_modules/decamelize/index.js +0 -13
  82. package/node_modules/decamelize/license +0 -21
  83. package/node_modules/decamelize/package.json +0 -38
  84. package/node_modules/decamelize/readme.md +0 -48
  85. package/node_modules/dijkstrajs/.travis.yml +0 -4
  86. package/node_modules/dijkstrajs/CONTRIBUTING.md +0 -8
  87. package/node_modules/dijkstrajs/LICENSE.md +0 -19
  88. package/node_modules/dijkstrajs/README.md +0 -18
  89. package/node_modules/dijkstrajs/dijkstra.js +0 -165
  90. package/node_modules/dijkstrajs/package.json +0 -60
  91. package/node_modules/dijkstrajs/test/dijkstra.test.js +0 -96
  92. package/node_modules/emoji-regex/LICENSE-MIT.txt +0 -20
  93. package/node_modules/emoji-regex/README.md +0 -73
  94. package/node_modules/emoji-regex/es2015/index.js +0 -6
  95. package/node_modules/emoji-regex/es2015/text.js +0 -6
  96. package/node_modules/emoji-regex/index.d.ts +0 -23
  97. package/node_modules/emoji-regex/index.js +0 -6
  98. package/node_modules/emoji-regex/package.json +0 -50
  99. package/node_modules/emoji-regex/text.js +0 -6
  100. package/node_modules/find-up/index.d.ts +0 -137
  101. package/node_modules/find-up/index.js +0 -89
  102. package/node_modules/find-up/license +0 -9
  103. package/node_modules/find-up/package.json +0 -53
  104. package/node_modules/find-up/readme.md +0 -156
  105. package/node_modules/get-caller-file/LICENSE.md +0 -6
  106. package/node_modules/get-caller-file/README.md +0 -41
  107. package/node_modules/get-caller-file/index.d.ts +0 -2
  108. package/node_modules/get-caller-file/index.js +0 -22
  109. package/node_modules/get-caller-file/index.js.map +0 -1
  110. package/node_modules/get-caller-file/package.json +0 -42
  111. package/node_modules/is-fullwidth-code-point/index.d.ts +0 -17
  112. package/node_modules/is-fullwidth-code-point/index.js +0 -50
  113. package/node_modules/is-fullwidth-code-point/license +0 -9
  114. package/node_modules/is-fullwidth-code-point/package.json +0 -42
  115. package/node_modules/is-fullwidth-code-point/readme.md +0 -39
  116. package/node_modules/locate-path/index.d.ts +0 -83
  117. package/node_modules/locate-path/index.js +0 -65
  118. package/node_modules/locate-path/license +0 -9
  119. package/node_modules/locate-path/package.json +0 -45
  120. package/node_modules/locate-path/readme.md +0 -122
  121. package/node_modules/p-limit/index.d.ts +0 -38
  122. package/node_modules/p-limit/index.js +0 -57
  123. package/node_modules/p-limit/license +0 -9
  124. package/node_modules/p-limit/package.json +0 -52
  125. package/node_modules/p-limit/readme.md +0 -101
  126. package/node_modules/p-locate/index.d.ts +0 -64
  127. package/node_modules/p-locate/index.js +0 -52
  128. package/node_modules/p-locate/license +0 -9
  129. package/node_modules/p-locate/package.json +0 -53
  130. package/node_modules/p-locate/readme.md +0 -90
  131. package/node_modules/p-try/index.d.ts +0 -39
  132. package/node_modules/p-try/index.js +0 -9
  133. package/node_modules/p-try/license +0 -9
  134. package/node_modules/p-try/package.json +0 -42
  135. package/node_modules/p-try/readme.md +0 -58
  136. package/node_modules/path-exists/index.d.ts +0 -28
  137. package/node_modules/path-exists/index.js +0 -23
  138. package/node_modules/path-exists/license +0 -9
  139. package/node_modules/path-exists/package.json +0 -39
  140. package/node_modules/path-exists/readme.md +0 -52
  141. package/node_modules/pngjs/.eslintignore +0 -2
  142. package/node_modules/pngjs/.eslintrc.json +0 -19
  143. package/node_modules/pngjs/.prettierignore +0 -3
  144. package/node_modules/pngjs/LICENSE +0 -20
  145. package/node_modules/pngjs/README.md +0 -429
  146. package/node_modules/pngjs/browser.js +0 -16096
  147. package/node_modules/pngjs/coverage/lcov-report/base.css +0 -354
  148. package/node_modules/pngjs/coverage/lcov-report/bitmapper.js.html +0 -881
  149. package/node_modules/pngjs/coverage/lcov-report/bitpacker.js.html +0 -554
  150. package/node_modules/pngjs/coverage/lcov-report/block-navigation.js +0 -77
  151. package/node_modules/pngjs/coverage/lcov-report/chunkstream.js.html +0 -647
  152. package/node_modules/pngjs/coverage/lcov-report/constants.js.html +0 -176
  153. package/node_modules/pngjs/coverage/lcov-report/crc.js.html +0 -200
  154. package/node_modules/pngjs/coverage/lcov-report/favicon.png +0 -0
  155. package/node_modules/pngjs/coverage/lcov-report/filter-pack.js.html +0 -593
  156. package/node_modules/pngjs/coverage/lcov-report/filter-parse-async.js.html +0 -152
  157. package/node_modules/pngjs/coverage/lcov-report/filter-parse-sync.js.html +0 -143
  158. package/node_modules/pngjs/coverage/lcov-report/filter-parse.js.html +0 -611
  159. package/node_modules/pngjs/coverage/lcov-report/format-normaliser.js.html +0 -359
  160. package/node_modules/pngjs/coverage/lcov-report/index.html +0 -426
  161. package/node_modules/pngjs/coverage/lcov-report/interlace.js.html +0 -365
  162. package/node_modules/pngjs/coverage/lcov-report/packer-async.js.html +0 -230
  163. package/node_modules/pngjs/coverage/lcov-report/packer-sync.js.html +0 -248
  164. package/node_modules/pngjs/coverage/lcov-report/packer.js.html +0 -467
  165. package/node_modules/pngjs/coverage/lcov-report/paeth-predictor.js.html +0 -128
  166. package/node_modules/pngjs/coverage/lcov-report/parser-async.js.html +0 -575
  167. package/node_modules/pngjs/coverage/lcov-report/parser-sync.js.html +0 -404
  168. package/node_modules/pngjs/coverage/lcov-report/parser.js.html +0 -950
  169. package/node_modules/pngjs/coverage/lcov-report/png-sync.js.html +0 -116
  170. package/node_modules/pngjs/coverage/lcov-report/png.js.html +0 -662
  171. package/node_modules/pngjs/coverage/lcov-report/prettify.css +0 -101
  172. package/node_modules/pngjs/coverage/lcov-report/prettify.js +0 -1007
  173. package/node_modules/pngjs/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  174. package/node_modules/pngjs/coverage/lcov-report/sorter.js +0 -169
  175. package/node_modules/pngjs/coverage/lcov-report/sync-inflate.js.html +0 -584
  176. package/node_modules/pngjs/coverage/lcov-report/sync-reader.js.html +0 -215
  177. package/node_modules/pngjs/coverage/lcov.info +0 -2177
  178. package/node_modules/pngjs/lib/bitmapper.js +0 -267
  179. package/node_modules/pngjs/lib/bitpacker.js +0 -158
  180. package/node_modules/pngjs/lib/chunkstream.js +0 -189
  181. package/node_modules/pngjs/lib/constants.js +0 -32
  182. package/node_modules/pngjs/lib/crc.js +0 -40
  183. package/node_modules/pngjs/lib/filter-pack.js +0 -171
  184. package/node_modules/pngjs/lib/filter-parse-async.js +0 -24
  185. package/node_modules/pngjs/lib/filter-parse-sync.js +0 -21
  186. package/node_modules/pngjs/lib/filter-parse.js +0 -177
  187. package/node_modules/pngjs/lib/format-normaliser.js +0 -93
  188. package/node_modules/pngjs/lib/interlace.js +0 -95
  189. package/node_modules/pngjs/lib/packer-async.js +0 -50
  190. package/node_modules/pngjs/lib/packer-sync.js +0 -56
  191. package/node_modules/pngjs/lib/packer.js +0 -129
  192. package/node_modules/pngjs/lib/paeth-predictor.js +0 -16
  193. package/node_modules/pngjs/lib/parser-async.js +0 -165
  194. package/node_modules/pngjs/lib/parser-sync.js +0 -108
  195. package/node_modules/pngjs/lib/parser.js +0 -290
  196. package/node_modules/pngjs/lib/png-sync.js +0 -12
  197. package/node_modules/pngjs/lib/png.js +0 -194
  198. package/node_modules/pngjs/lib/sync-inflate.js +0 -168
  199. package/node_modules/pngjs/lib/sync-reader.js +0 -45
  200. package/node_modules/pngjs/package.json +0 -73
  201. package/node_modules/qrcode/README.md +0 -770
  202. package/node_modules/qrcode/bin/qrcode +0 -159
  203. package/node_modules/qrcode/helper/to-sjis-browser.js +0 -2
  204. package/node_modules/qrcode/helper/to-sjis.js +0 -105
  205. package/node_modules/qrcode/lib/browser.js +0 -76
  206. package/node_modules/qrcode/lib/can-promise.js +0 -7
  207. package/node_modules/qrcode/lib/core/alignment-pattern.js +0 -83
  208. package/node_modules/qrcode/lib/core/alphanumeric-data.js +0 -59
  209. package/node_modules/qrcode/lib/core/bit-buffer.js +0 -37
  210. package/node_modules/qrcode/lib/core/bit-matrix.js +0 -65
  211. package/node_modules/qrcode/lib/core/byte-data.js +0 -30
  212. package/node_modules/qrcode/lib/core/error-correction-code.js +0 -135
  213. package/node_modules/qrcode/lib/core/error-correction-level.js +0 -50
  214. package/node_modules/qrcode/lib/core/finder-pattern.js +0 -22
  215. package/node_modules/qrcode/lib/core/format-info.js +0 -29
  216. package/node_modules/qrcode/lib/core/galois-field.js +0 -69
  217. package/node_modules/qrcode/lib/core/kanji-data.js +0 -54
  218. package/node_modules/qrcode/lib/core/mask-pattern.js +0 -234
  219. package/node_modules/qrcode/lib/core/mode.js +0 -167
  220. package/node_modules/qrcode/lib/core/numeric-data.js +0 -43
  221. package/node_modules/qrcode/lib/core/polynomial.js +0 -62
  222. package/node_modules/qrcode/lib/core/qrcode.js +0 -495
  223. package/node_modules/qrcode/lib/core/reed-solomon-encoder.js +0 -56
  224. package/node_modules/qrcode/lib/core/regex.js +0 -31
  225. package/node_modules/qrcode/lib/core/segments.js +0 -330
  226. package/node_modules/qrcode/lib/core/utils.js +0 -63
  227. package/node_modules/qrcode/lib/core/version-check.js +0 -9
  228. package/node_modules/qrcode/lib/core/version.js +0 -163
  229. package/node_modules/qrcode/lib/index.js +0 -12
  230. package/node_modules/qrcode/lib/renderer/canvas.js +0 -63
  231. package/node_modules/qrcode/lib/renderer/png.js +0 -78
  232. package/node_modules/qrcode/lib/renderer/svg-tag.js +0 -81
  233. package/node_modules/qrcode/lib/renderer/svg.js +0 -19
  234. package/node_modules/qrcode/lib/renderer/terminal/terminal-small.js +0 -85
  235. package/node_modules/qrcode/lib/renderer/terminal/terminal.js +0 -49
  236. package/node_modules/qrcode/lib/renderer/terminal.js +0 -9
  237. package/node_modules/qrcode/lib/renderer/utf8.js +0 -71
  238. package/node_modules/qrcode/lib/renderer/utils.js +0 -99
  239. package/node_modules/qrcode/lib/server.js +0 -138
  240. package/node_modules/qrcode/license +0 -10
  241. package/node_modules/qrcode/package.json +0 -77
  242. package/node_modules/require-directory/.jshintrc +0 -67
  243. package/node_modules/require-directory/.npmignore +0 -1
  244. package/node_modules/require-directory/.travis.yml +0 -3
  245. package/node_modules/require-directory/LICENSE +0 -22
  246. package/node_modules/require-directory/README.markdown +0 -184
  247. package/node_modules/require-directory/index.js +0 -86
  248. package/node_modules/require-directory/package.json +0 -40
  249. package/node_modules/require-main-filename/CHANGELOG.md +0 -26
  250. package/node_modules/require-main-filename/LICENSE.txt +0 -14
  251. package/node_modules/require-main-filename/README.md +0 -26
  252. package/node_modules/require-main-filename/index.js +0 -18
  253. package/node_modules/require-main-filename/package.json +0 -35
  254. package/node_modules/set-blocking/CHANGELOG.md +0 -26
  255. package/node_modules/set-blocking/LICENSE.txt +0 -14
  256. package/node_modules/set-blocking/README.md +0 -31
  257. package/node_modules/set-blocking/index.js +0 -7
  258. package/node_modules/set-blocking/package.json +0 -42
  259. package/node_modules/string-width/index.d.ts +0 -29
  260. package/node_modules/string-width/index.js +0 -47
  261. package/node_modules/string-width/license +0 -9
  262. package/node_modules/string-width/package.json +0 -56
  263. package/node_modules/string-width/readme.md +0 -50
  264. package/node_modules/strip-ansi/index.d.ts +0 -17
  265. package/node_modules/strip-ansi/index.js +0 -4
  266. package/node_modules/strip-ansi/license +0 -9
  267. package/node_modules/strip-ansi/package.json +0 -54
  268. package/node_modules/strip-ansi/readme.md +0 -46
  269. package/node_modules/which-module/LICENSE +0 -13
  270. package/node_modules/which-module/README.md +0 -58
  271. package/node_modules/which-module/index.js +0 -9
  272. package/node_modules/which-module/package.json +0 -41
  273. package/node_modules/wrap-ansi/index.js +0 -186
  274. package/node_modules/wrap-ansi/license +0 -9
  275. package/node_modules/wrap-ansi/package.json +0 -61
  276. package/node_modules/wrap-ansi/readme.md +0 -97
  277. package/node_modules/y18n/CHANGELOG.md +0 -35
  278. package/node_modules/y18n/LICENSE +0 -13
  279. package/node_modules/y18n/README.md +0 -109
  280. package/node_modules/y18n/index.js +0 -188
  281. package/node_modules/y18n/package.json +0 -39
  282. package/node_modules/yargs/CHANGELOG.md +0 -420
  283. package/node_modules/yargs/LICENSE +0 -21
  284. package/node_modules/yargs/README.md +0 -140
  285. package/node_modules/yargs/build/lib/apply-extends.d.ts +0 -2
  286. package/node_modules/yargs/build/lib/apply-extends.js +0 -65
  287. package/node_modules/yargs/build/lib/argsert.d.ts +0 -2
  288. package/node_modules/yargs/build/lib/argsert.js +0 -65
  289. package/node_modules/yargs/build/lib/command.d.ts +0 -64
  290. package/node_modules/yargs/build/lib/command.js +0 -416
  291. package/node_modules/yargs/build/lib/common-types.d.ts +0 -36
  292. package/node_modules/yargs/build/lib/common-types.js +0 -25
  293. package/node_modules/yargs/build/lib/completion-templates.d.ts +0 -2
  294. package/node_modules/yargs/build/lib/completion-templates.js +0 -50
  295. package/node_modules/yargs/build/lib/completion.d.ts +0 -21
  296. package/node_modules/yargs/build/lib/completion.js +0 -135
  297. package/node_modules/yargs/build/lib/is-promise.d.ts +0 -1
  298. package/node_modules/yargs/build/lib/is-promise.js +0 -9
  299. package/node_modules/yargs/build/lib/levenshtein.d.ts +0 -1
  300. package/node_modules/yargs/build/lib/levenshtein.js +0 -58
  301. package/node_modules/yargs/build/lib/middleware.d.ts +0 -10
  302. package/node_modules/yargs/build/lib/middleware.js +0 -57
  303. package/node_modules/yargs/build/lib/obj-filter.d.ts +0 -1
  304. package/node_modules/yargs/build/lib/obj-filter.js +0 -14
  305. package/node_modules/yargs/build/lib/parse-command.d.ts +0 -11
  306. package/node_modules/yargs/build/lib/parse-command.js +0 -36
  307. package/node_modules/yargs/build/lib/process-argv.d.ts +0 -2
  308. package/node_modules/yargs/build/lib/process-argv.js +0 -31
  309. package/node_modules/yargs/build/lib/usage.d.ts +0 -49
  310. package/node_modules/yargs/build/lib/usage.js +0 -540
  311. package/node_modules/yargs/build/lib/validation.d.ts +0 -34
  312. package/node_modules/yargs/build/lib/validation.js +0 -330
  313. package/node_modules/yargs/build/lib/yargs.d.ts +0 -274
  314. package/node_modules/yargs/build/lib/yargs.js +0 -1190
  315. package/node_modules/yargs/build/lib/yerror.d.ts +0 -4
  316. package/node_modules/yargs/build/lib/yerror.js +0 -11
  317. package/node_modules/yargs/index.js +0 -40
  318. package/node_modules/yargs/locales/be.json +0 -46
  319. package/node_modules/yargs/locales/de.json +0 -46
  320. package/node_modules/yargs/locales/en.json +0 -51
  321. package/node_modules/yargs/locales/es.json +0 -46
  322. package/node_modules/yargs/locales/fi.json +0 -49
  323. package/node_modules/yargs/locales/fr.json +0 -53
  324. package/node_modules/yargs/locales/hi.json +0 -49
  325. package/node_modules/yargs/locales/hu.json +0 -46
  326. package/node_modules/yargs/locales/id.json +0 -50
  327. package/node_modules/yargs/locales/it.json +0 -46
  328. package/node_modules/yargs/locales/ja.json +0 -51
  329. package/node_modules/yargs/locales/ko.json +0 -49
  330. package/node_modules/yargs/locales/nb.json +0 -44
  331. package/node_modules/yargs/locales/nl.json +0 -49
  332. package/node_modules/yargs/locales/nn.json +0 -44
  333. package/node_modules/yargs/locales/pirate.json +0 -13
  334. package/node_modules/yargs/locales/pl.json +0 -49
  335. package/node_modules/yargs/locales/pt.json +0 -45
  336. package/node_modules/yargs/locales/pt_BR.json +0 -48
  337. package/node_modules/yargs/locales/ru.json +0 -46
  338. package/node_modules/yargs/locales/th.json +0 -46
  339. package/node_modules/yargs/locales/tr.json +0 -48
  340. package/node_modules/yargs/locales/zh_CN.json +0 -48
  341. package/node_modules/yargs/locales/zh_TW.json +0 -47
  342. package/node_modules/yargs/package.json +0 -92
  343. package/node_modules/yargs/yargs.js +0 -14
  344. package/node_modules/yargs-parser/CHANGELOG.md +0 -601
  345. package/node_modules/yargs-parser/LICENSE.txt +0 -14
  346. package/node_modules/yargs-parser/README.md +0 -449
  347. package/node_modules/yargs-parser/index.js +0 -1032
  348. package/node_modules/yargs-parser/lib/tokenize-arg-string.js +0 -40
  349. package/node_modules/yargs-parser/package.json +0 -46
@@ -1,309 +0,0 @@
1
- // 插件侧的**更新判定**(CONTEXT「内容标识」「对齐提示」「降级指引」)。
2
- //
3
- // 这里只放纯逻辑,不做任何 I/O 决策之外的事:
4
- // - 规范化发布清单;
5
- // - 解析 tgz 的下载地址(清单里给 `url`,否则相对清单自身地址解析);
6
- // - 比较「本地内容标识 vs 清单内容标识」决定要不要下载(版本号只做提示与排序);
7
- // - 识别本机的**安装形态**,据此给出可执行动作或降级指引。
8
- // 下载、校验、落位、pnpm、重启属于执行面,不在这里(见后续阶段的执行模块)。
9
- //
10
- // 三条判据互不代替:能力能不能用看能力声明;要不要下载看**内容标识**;要不要提示看版本号。
11
- import { createHash } from "node:crypto";
12
-
13
- /** 本模块能处理的清单 schema。 */
14
- export const SUPPORTED_MANIFEST_SCHEMA = 1;
15
-
16
- /** 更新判定结果。 */
17
- export const UPDATE_ACTION = {
18
- /** 本地内容与清单一致:已是最新。 */
19
- UP_TO_DATE: "up-to-date",
20
- /** 清单内容与本地不同、且不早于本地:应更新。 */
21
- UPDATE_AVAILABLE: "update-available",
22
- /** 本地比清单新(例如本地装了更新的自建构建):不降级。 */
23
- LOCAL_AHEAD: "local-ahead",
24
- /** 清单不可用或字段缺失:无法判断,如实报出。 */
25
- UNKNOWN: "unknown",
26
- };
27
-
28
- /** 安装形态(决定升级动作,见 docs/06-install-run.md 的三种方式)。 */
29
- export const INSTALL_SHAPE = {
30
- /** 从 npm/registry 解析(`^3.2.0` 之类)。 */
31
- REGISTRY: "registry",
32
- /** `github:owner/repo#tag` 这类 git 依赖。 */
33
- GITHUB_TAG: "github-tag",
34
- /** `file:` 指向一个本地 clone(含 .git)。 */
35
- FILE_CLONE: "file-clone",
36
- /** `file:` 指向一个已解开的目录(非 git 仓库)。 */
37
- FILE_DIR: "file-dir",
38
- /** `file:` 指向一个 .tgz 包。 */
39
- TGZ_FILE: "tgz-file",
40
- /** 无法识别。 */
41
- UNKNOWN: "unknown",
42
- };
43
-
44
- /** 更新执行方式:能自动完成,还是只能给出可复制的指引。 */
45
- export const UPDATE_MODE = {
46
- AUTO: "auto",
47
- GUIDANCE: "guidance",
48
- };
49
-
50
- /**
51
- * 解析 `X.Y.Z`、可选 SemVer prerelease(如 `3.2.1-alpha.1`)与数字 `+N` build 号;不合法返回 null。
52
- * `version` 是**只含 core** 的 `X.Y.Z` 字串:两端配对(App 的 `X.Y.Z+N` 与插件)只比它,
53
- * prerelease 只影响插件自身的排序(见 compareVersion)。
54
- */
55
- export function parseVersion(value) {
56
- const m = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+(\d+))?$/
57
- .exec(typeof value === "string" ? value.trim() : "");
58
- if (!m) return null;
59
- const prerelease = m[4] === undefined ? [] : m[4].split(".");
60
- // SemVer §9:纯数字的 prerelease 标识符不得有前导零(`alpha.01` 非法,`alpha.1` 合法)。
61
- for (const identifier of prerelease) {
62
- if (/^\d+$/.test(identifier) && identifier.length > 1 && identifier.startsWith("0")) return null;
63
- }
64
- return {
65
- major: +m[1],
66
- minor: +m[2],
67
- patch: +m[3],
68
- version: `${+m[1]}.${+m[2]}.${+m[3]}`,
69
- prerelease,
70
- build: m[5] === undefined ? null : +m[5],
71
- };
72
- }
73
-
74
- /** 比较两个 prerelease 标识符(SemVer §11.4:数字按数值比、数字 < 字母数字、字母数字按 ASCII)。 */
75
- function comparePrereleaseIdentifier(a, b) {
76
- const numericA = /^\d+$/.test(a);
77
- const numericB = /^\d+$/.test(b);
78
- if (numericA && numericB) {
79
- // 无前导零的数字串:先比长度再比字典序即等价于按数值比较(超长数字也不丢精度)。
80
- if (a.length !== b.length) return a.length < b.length ? -1 : 1;
81
- if (a === b) return 0;
82
- return a < b ? -1 : 1;
83
- }
84
- if (numericA) return -1;
85
- if (numericB) return 1;
86
- if (a === b) return 0;
87
- return a < b ? -1 : 1;
88
- }
89
-
90
- /** 比较 prerelease 段:无 prerelease 的版本优先级更高(3.2.1 > 3.2.1-alpha.1)。 */
91
- function comparePrerelease(a, b) {
92
- const left = a.prerelease;
93
- const right = b.prerelease;
94
- if (left.length === 0 && right.length === 0) return 0;
95
- if (left.length === 0) return 1;
96
- if (right.length === 0) return -1;
97
- const length = Math.min(left.length, right.length);
98
- for (let index = 0; index < length; index++) {
99
- const comparison = comparePrereleaseIdentifier(left[index], right[index]);
100
- if (comparison !== 0) return comparison;
101
- }
102
- if (left.length === right.length) return 0;
103
- return left.length < right.length ? -1 : 1;
104
- }
105
-
106
- /**
107
- * 比较两个版本号,返回 -1 / 0 / 1;任一不可解析时返回 null。
108
- * 主序按 SemVer:core → prerelease(`3.2.1-alpha.1 < 3.2.1`、`alpha.2 > alpha.1`)。
109
- * `+build` 是本仓既有扩展:只在 core 与 prerelease 都相同时参与比较
110
- * (App 侧用它判断"有没有更新";两端配对看的是 core,见 compareSemanticVersion)。
111
- */
112
- export function compareVersion(a, b) {
113
- const pa = parseVersion(a);
114
- const pb = parseVersion(b);
115
- if (!pa || !pb) return null;
116
- for (const k of ["major", "minor", "patch"]) {
117
- if (pa[k] !== pb[k]) return pa[k] < pb[k] ? -1 : 1;
118
- }
119
- const prerelease = comparePrerelease(pa, pb);
120
- if (prerelease !== 0) return prerelease;
121
- if (pa.build !== null && pb.build !== null && pa.build !== pb.build) return pa.build < pb.build ? -1 : 1;
122
- return 0;
123
- }
124
-
125
- /**
126
- * 规范化发布清单。只接受 schema 1;`plugin` 段是插件自更新所必需的部分。
127
- * 缺失字段不猜测:把可选的留空、必需的报错。
128
- */
129
- export function normalizeReleaseManifest(raw) {
130
- if (!raw || typeof raw !== "object") return { ok: false, error: "manifest-not-object" };
131
- if (raw.schema !== SUPPORTED_MANIFEST_SCHEMA) {
132
- return { ok: false, error: "manifest-schema-unsupported", schema: raw.schema };
133
- }
134
- const p = raw.plugin;
135
- if (!p || typeof p !== "object") return { ok: false, error: "manifest-plugin-missing" };
136
- if (typeof p.version !== "string" || !parseVersion(p.version)) return { ok: false, error: "manifest-plugin-version-invalid" };
137
- if (typeof p.file !== "string" || p.file === "") return { ok: false, error: "manifest-plugin-file-missing" };
138
- const sha256 = typeof p.sha256 === "string" ? p.sha256.toLowerCase() : "";
139
- if (!/^[0-9a-f]{64}$/.test(sha256)) return { ok: false, error: "manifest-plugin-sha256-invalid" };
140
- const contentSha256 = typeof p.contentSha256 === "string" ? p.contentSha256.toLowerCase() : "";
141
- if (contentSha256 !== "" && !/^[0-9a-f]{64}$/.test(contentSha256)) {
142
- return { ok: false, error: "manifest-plugin-content-sha256-invalid" };
143
- }
144
- return {
145
- ok: true,
146
- schema: raw.schema,
147
- plugin: {
148
- version: p.version,
149
- file: p.file,
150
- url: typeof p.url === "string" && p.url !== "" ? p.url : "",
151
- size: Number.isFinite(p.size) ? p.size : null,
152
- sha256,
153
- contentSha256,
154
- },
155
- app: raw.app && typeof raw.app === "object" ? raw.app : null,
156
- };
157
- }
158
-
159
- /**
160
- * 解析插件包的下载地址:清单给了 `url` 就用它,否则相对**清单自身地址**解析。
161
- * 两者都没有则返回空串(调用方按"来源不可用"处理)。
162
- */
163
- export function resolvePluginUrl(manifestUrl, plugin) {
164
- if (plugin?.url) return plugin.url;
165
- if (!manifestUrl || !plugin?.file) return "";
166
- try {
167
- return new URL(plugin.file, manifestUrl).toString();
168
- } catch {
169
- return "";
170
- }
171
- }
172
-
173
- /**
174
- * 决定要不要下载新版本。
175
- *
176
- * 判据是**内容标识**:版本号相同而内容不同(真实存在的形态)必须被识别为"应更新"。
177
- * 缺少内容标识时退化为版本比较,并把这一盲区写进 `reason`,不假装判断是完整的。
178
- */
179
- export function decidePluginUpdate({ localVersion, localContentSha256, remote }) {
180
- if (!remote || typeof remote !== "object" || !parseVersion(remote.version)) {
181
- return { action: UPDATE_ACTION.UNKNOWN, reason: "remote-version-missing" };
182
- }
183
- const comparison = compareVersion(remote.version, localVersion ?? "");
184
- const localDigest = (localContentSha256 ?? "").toLowerCase();
185
- const remoteDigest = (remote.contentSha256 ?? "").toLowerCase();
186
-
187
- if (localDigest && remoteDigest) {
188
- if (localDigest === remoteDigest) {
189
- return { action: UPDATE_ACTION.UP_TO_DATE, reason: "content-identical", comparison };
190
- }
191
- if (comparison !== null && comparison < 0) {
192
- return { action: UPDATE_ACTION.LOCAL_AHEAD, reason: "content-differs-local-newer", comparison };
193
- }
194
- return {
195
- action: UPDATE_ACTION.UPDATE_AVAILABLE,
196
- reason: comparison === 0 ? "content-differs-same-version" : "content-differs-remote-newer",
197
- comparison,
198
- };
199
- }
200
-
201
- if (comparison === null) return { action: UPDATE_ACTION.UNKNOWN, reason: "local-version-unparseable" };
202
- if (comparison > 0) return { action: UPDATE_ACTION.UPDATE_AVAILABLE, reason: "version-newer-no-content-digest", comparison };
203
- if (comparison < 0) return { action: UPDATE_ACTION.LOCAL_AHEAD, reason: "version-older-no-content-digest", comparison };
204
- // 版本相同又无内容标识:这是"看不见版本号相同内容不同"的盲区,如实标注
205
- return { action: UPDATE_ACTION.UP_TO_DATE, reason: "version-equal-no-content-digest", comparison };
206
- }
207
-
208
- /** 从依赖声明推断安装形态;纯字符串判断,不碰文件系统。 */
209
- export function classifySpecifier(specifier) {
210
- const s = typeof specifier === "string" ? specifier.trim() : "";
211
- if (s === "") return INSTALL_SHAPE.UNKNOWN;
212
- if (/^github:/i.test(s)) return INSTALL_SHAPE.GITHUB_TAG;
213
- if (/^link:/i.test(s)) return INSTALL_SHAPE.UNKNOWN;
214
- if (/^file:/i.test(s)) {
215
- const target = s.slice("file:".length).toLowerCase();
216
- if (target.endsWith(".tgz") || target.endsWith(".tar.gz")) return INSTALL_SHAPE.TGZ_FILE;
217
- return INSTALL_SHAPE.FILE_DIR; // 目录形态;是否 git clone 需要文件系统确认
218
- }
219
- if (/^[\^~]?\d+\.\d+\.\d+/.test(s) || /^(>|>=|<|<=|=|\*|latest)/.test(s)) return INSTALL_SHAPE.REGISTRY;
220
- return INSTALL_SHAPE.UNKNOWN;
221
- }
222
-
223
- /**
224
- * 探测安装形态。`specifier` 来自 profile 的 `package.json`;目录形态再用注入的谓词确认
225
- * 是否 git 仓库。`deps` 可注入,便于在不碰真实文件系统的测试里驱动。
226
- */
227
- export function detectInstallShape({ specifier, dirPath = "", isGitRepo = () => false } = {}) {
228
- const base = classifySpecifier(specifier);
229
- if (base === INSTALL_SHAPE.FILE_DIR && dirPath && isGitRepo(dirPath)) return INSTALL_SHAPE.FILE_CLONE;
230
- return base;
231
- }
232
-
233
- /**
234
- * 该形态下如何更新。`auto` 表示本机可以自行完成(改依赖声明 + 安装 + 重启);
235
- * `guidance` 表示只能给出**与实际安装方式对应的那一条**指引,不并列三条让用户猜。
236
- */
237
- export function planForShape(shape, { specifier = "", dirPath = "" } = {}) {
238
- const installCmd = "pnpm install";
239
- switch (shape) {
240
- case INSTALL_SHAPE.REGISTRY:
241
- return {
242
- mode: UPDATE_MODE.AUTO,
243
- shape,
244
- summary: "从 npm 源升级:改写 profile 依赖版本后重新安装",
245
- steps: [`把 profile 的依赖版本改为新版(当前 ${specifier || "未知"})`, "重新安装依赖", "重启 DSH 让插件生效"],
246
- commands: [installCmd],
247
- };
248
- case INSTALL_SHAPE.GITHUB_TAG:
249
- return {
250
- mode: UPDATE_MODE.AUTO,
251
- shape,
252
- summary: "git 依赖升级:把 tag 改到新版本后重新安装",
253
- steps: [`把 profile 依赖的 tag 改为 v<新版>(当前 ${specifier || "未知"})`, "重新安装依赖", "重启 DSH 让插件生效"],
254
- commands: [installCmd],
255
- };
256
- case INSTALL_SHAPE.FILE_CLONE:
257
- return {
258
- mode: UPDATE_MODE.AUTO,
259
- shape,
260
- summary: "本地 clone 升级:拉取新代码后重新安装",
261
- steps: [`在 clone 目录拉取新版本${dirPath ? `(${dirPath})` : ""}`, "重新安装依赖", "重启 DSH 让插件生效"],
262
- commands: [dirPath ? `git -C ${dirPath} pull` : "git pull", installCmd],
263
- };
264
- case INSTALL_SHAPE.FILE_DIR:
265
- return {
266
- mode: UPDATE_MODE.AUTO,
267
- shape,
268
- summary: "本地目录升级:用新版内容替换该目录后重新安装",
269
- steps: [`用新版插件内容替换该目录${dirPath ? `(${dirPath})` : ""}`, "重新安装依赖", "重启 DSH 让插件生效"],
270
- commands: [installCmd],
271
- };
272
- case INSTALL_SHAPE.TGZ_FILE:
273
- return {
274
- mode: UPDATE_MODE.AUTO,
275
- shape,
276
- summary: "tgz 安装升级:换上新版 tgz 后重新安装",
277
- steps: [`下载新版 tgz 并替换${dirPath ? `(${dirPath})` : "原包"}`, "重新安装依赖", "重启 DSH 让插件生效"],
278
- commands: [installCmd],
279
- };
280
- default:
281
- return {
282
- mode: UPDATE_MODE.GUIDANCE,
283
- shape: INSTALL_SHAPE.UNKNOWN,
284
- summary: "无法识别本机插件安装方式,按 docs/06-install-run.md 的三种方式之一处理",
285
- steps: ["确认插件是通过 clone、git tag 还是 tgz 安装的", "按对应方式更新到新版", "重启 DSH 让插件生效"],
286
- commands: [],
287
- };
288
- }
289
- }
290
-
291
- /**
292
- * 对齐提示用的比较:只比 core `X.Y.Z`,`prerelease` 与 `+build` 都不参与。
293
- * 契约:插件 `3.2.1-alpha.1` 与 App `3.2.1+24` 按 core `3.2.1` 视为对齐;
294
- * 同一发布周期内每次构建/预发布都不应把提示刷成噪声。
295
- */
296
- export function compareSemanticVersion(a, b) {
297
- const pa = parseVersion(a);
298
- const pb = parseVersion(b);
299
- if (!pa || !pb) return null;
300
- for (const k of ["major", "minor", "patch"]) {
301
- if (pa[k] !== pb[k]) return pa[k] < pb[k] ? -1 : 1;
302
- }
303
- return 0;
304
- }
305
-
306
- /** 计算某个文件的 sha256(发布侧与运行侧共用同一写法,避免大小写/编码差异)。 */
307
- export function sha256Hex(buf) {
308
- return createHash("sha256").update(buf).digest("hex");
309
- }
@@ -1,345 +0,0 @@
1
- // 可回滚包的独立运行闭包。内容标识只覆盖插件代码;本模块的 digest 覆盖整个冻结包。
2
- // 根只复制 package.json + lib,依赖按 Node 实际解析的包身份冻结(不复制 profile/secret)。
3
- //
4
- // App 载体排除(设计稿 §5.5):载体是随包的**数据依赖**(72MB APK),不是运行依赖。
5
- // 只有「根包显式声明 + 身份校验通过」的载体在**建图阶段**(要求可执行入口之前)被跳过;
6
- // 建图、冻结复制、冻结后闭包校验、快照复用与监督器恢复都走同一条判定
7
- // `appCarrierExclusion`,普通依赖自报 `dsh.appCarrier` 不构成豁免。
8
- import { createHash } from "node:crypto";
9
- import {
10
- chmodSync, copyFileSync, lstatSync, mkdirSync, mkdtempSync, readFileSync,
11
- readdirSync, readlinkSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, writeFileSync,
12
- } from "node:fs";
13
- import { tmpdir } from "node:os";
14
- import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
15
- import { spawnSync } from "node:child_process";
16
- import { appCarrierExclusion } from "./app-carrier.js";
17
-
18
- export const RUNTIME_COMPLETE_MARKER = ".complete";
19
- const MARKER_SCHEMA = 1;
20
-
21
- function contained(root, target) {
22
- const rel = relative(root, target);
23
- return rel === "" || (rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel));
24
- }
25
-
26
- // 目标可能尚不存在;先解析已有祖先的 symlink,禁止借路径别名把 staging 写回源。
27
- function canonicalDestination(destination) {
28
- const missing = [basename(destination)];
29
- let parent = dirname(destination);
30
- while (true) {
31
- try { return join(realpathSync(parent), ...missing); }
32
- catch (error) {
33
- if (error.code !== "ENOENT") throw error;
34
- try {
35
- lstatSync(parent); // 已有 broken symlink 不是可创建的缺失祖先。
36
- throw new Error("snapshot-destination-broken-ancestor");
37
- } catch (entryError) { if (entryError.code !== "ENOENT") throw entryError; }
38
- missing.unshift(basename(parent));
39
- parent = dirname(parent);
40
- }
41
- }
42
- }
43
-
44
- // 全新的 Node 进程:无继承的 loader/NODE_OPTIONS、NODE_PATH、HOME 或 module cache。
45
- // 静态 dependencies 图查缺失的 lazy 依赖;加载钩子校验实际加载图(含未声明 require/import)。
46
- const WORKER = String.raw`
47
- import fs from 'node:fs';
48
- import path from 'node:path';
49
- import Module, { createRequire, isBuiltin } from 'node:module';
50
- import { fileURLToPath } from 'node:url';
51
- const input = JSON.parse(fs.readFileSync(0, 'utf8'));
52
- const root = fs.realpathSync(input.root);
53
- const loaded = new Set();
54
- const contains = (target) => {
55
- const rel = path.relative(root, target);
56
- return rel === '' || (rel !== '..' && !rel.startsWith('..' + path.sep) && !path.isAbsolute(rel));
57
- };
58
- const readPackage = (dir) => JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'));
59
- function binding(req, name, optional = false) {
60
- if (!/^(?:@[^/\s]+\/)?[^./\s][^/\s]*$/.test(name) || isBuiltin(name)) throw new Error(name + ':invalid-dependency-name');
61
- let source;
62
- for (const lookup of req.resolve.paths(name) ?? []) {
63
- const candidate = path.join(lookup, name);
64
- // scoped 容器本身的 broken symlink 也不能被上层同名包兜底。
65
- if (name.startsWith('@')) {
66
- const scope = path.join(lookup, name.split('/')[0]);
67
- let scopeStat;
68
- try { scopeStat = fs.lstatSync(scope); }
69
- catch (error) { if (error.code !== 'ENOENT') throw error; }
70
- if (scopeStat?.isSymbolicLink()) {
71
- try { fs.realpathSync(scope); }
72
- catch (error) { throw new Error(name + ':broken-required-link(' + error.code + ')'); }
73
- }
74
- }
75
- try { fs.lstatSync(candidate); }
76
- catch (error) { if (error.code === 'ENOENT') continue; throw error; }
77
- try { source = fs.realpathSync(candidate); }
78
- catch (error) { throw new Error(name + ':broken-required-link(' + error.code + ')'); }
79
- if (!fs.statSync(source).isDirectory()) throw new Error(name + ':dependency-not-directory');
80
- break;
81
- }
82
- if (!source) {
83
- if (optional) return null;
84
- throw new Error(name + ':无法解析(MODULE_NOT_FOUND)');
85
- }
86
- let entry;
87
- try { entry = fs.realpathSync(req.resolve(name)); }
88
- catch (error) { throw new Error(name + ':无法解析(' + error.code + ':' + error.message + ')'); }
89
- if (input.mode === 'verify' && (!contains(source) || !contains(entry))) {
90
- throw new Error(name + ':解析到解包目录之外(' + entry + ')');
91
- }
92
- return { source, entry };
93
- }
94
- function graph() {
95
- const nodes = [{ source: root, dependencies: Object.create(null) }];
96
- const ids = new Map([[root, 0]]);
97
- // 只有父进程用「根包声明 + 完整身份校验」确认过的载体别名才会出现在 input.carrier。
98
- // 必须在要求可执行入口(binding 的 req.resolve)之前跳过它:载体是数据包,没有 main/exports。
99
- const excludedAlias = typeof input.carrier?.alias === 'string' ? input.carrier.alias : null;
100
- for (let index = 0; index < nodes.length; index++) {
101
- if (nodes.length > 4096) throw new Error('runtime-closure-too-large');
102
- const node = nodes[index];
103
- const pkg = readPackage(node.source);
104
- node.name = pkg.name ?? '';
105
- const req = createRequire(path.join(node.source, 'package.json'));
106
- const deps = { ...pkg.dependencies, ...pkg.optionalDependencies };
107
- for (const name of Object.keys(deps).sort()) {
108
- // 仅根包自己的载体别名豁免;其他包即使同名依赖也照常进入闭包。
109
- if (index === 0 && excludedAlias !== null && name === excludedAlias) continue;
110
- const found = binding(req, name, Object.hasOwn(pkg.optionalDependencies ?? {}, name));
111
- if (!found) continue;
112
- let id = ids.get(found.source);
113
- if (id === undefined) {
114
- id = nodes.length;
115
- ids.set(found.source, id);
116
- nodes.push({ source: found.source, dependencies: Object.create(null) });
117
- }
118
- node.dependencies[name] = id;
119
- }
120
- }
121
- return nodes;
122
- }
123
- function checkLoad(filename) {
124
- if (isBuiltin(filename)) return;
125
- const real = fs.realpathSync(filename);
126
- if (!contains(real)) throw new Error('runtime-load-outside-package(' + real + ')');
127
- loaded.add(real);
128
- }
129
- async function run() {
130
- const nodes = graph();
131
- if (input.mode === 'graph') return { ok: true, nodes };
132
- if (typeof Module.registerHooks !== 'function') throw new Error('runtime-closure-verification-requires-Node-registerHooks');
133
- const original = Module._resolveFilename;
134
- Module._resolveFilename = function(...args) {
135
- const filename = original.apply(this, args);
136
- checkLoad(filename);
137
- return filename;
138
- };
139
- // 新版 Node 的同步 hooks 同时覆盖传递 ESM import 和 CJS require。
140
- Module.registerHooks({ resolve(specifier, context, nextResolve) {
141
- const result = nextResolve(specifier, context);
142
- if (result.url.startsWith('file:')) checkLoad(fileURLToPath(result.url));
143
- else if (!result.url.startsWith('node:')) throw new Error('runtime-load-unsupported-url(' + result.url + ')');
144
- return result;
145
- } });
146
- for (const node of nodes) {
147
- const req = createRequire(path.join(node.source, 'package.json'));
148
- for (const [name, id] of Object.entries(node.dependencies)) {
149
- try {
150
- const dependency = req(name);
151
- if (name === 'qrcode' || nodes[id].name === 'qrcode') {
152
- if (typeof dependency.toBuffer !== 'function') throw new Error('qrcode.toBuffer missing');
153
- const png = await dependency.toBuffer('dsh-runtime-closure-smoke', { type: 'png', width: 64, margin: 1, errorCorrectionLevel: 'M' });
154
- if (!Buffer.isBuffer(png) || !png.subarray(0, 8).equals(Buffer.from([137,80,78,71,13,10,26,10]))) {
155
- throw new Error('qrcode.toBuffer did not produce PNG');
156
- }
157
- }
158
- } catch (error) { throw new Error(name + ':加载失败(' + error.message + ')'); }
159
- }
160
- }
161
- return { ok: true, dependencies: Object.keys(nodes[0].dependencies), packageCount: nodes.length, loadedFiles: [...loaded].sort() };
162
- }
163
- try { process.stdout.write('\n__DSH_CLOSURE_RESULT__' + JSON.stringify(await run()) + '\n'); }
164
- catch (error) { process.stdout.write('\n__DSH_CLOSURE_RESULT__' + JSON.stringify({ ok: false, error: error.message }) + '\n'); process.exitCode = 1; }
165
- `;
166
-
167
- function cleanupTemporary(dir, parent) {
168
- // 删除前必须核验实际绝对目标:只能是本次 mkdtemp 分配的直接子目录。
169
- if (!isAbsolute(dir) || dirname(dir) !== parent || !basename(dir).startsWith(".dsh-runtime-")) {
170
- throw new Error("unsafe-temporary-cleanup");
171
- }
172
- rmSync(dir, { recursive: true, force: true });
173
- }
174
-
175
- function runWorker(root, mode, carrier = null) {
176
- const parent = realpathSync(tmpdir());
177
- const scratch = mkdtempSync(join(parent, ".dsh-runtime-check-"));
178
- try {
179
- const child = spawnSync(process.execPath, ["--no-global-search-paths", "--input-type=module", "-e", WORKER], {
180
- input: JSON.stringify({ root, mode, carrier }), encoding: "utf8", cwd: scratch,
181
- env: { PATH: process.env.PATH ?? "", HOME: scratch, TMPDIR: scratch, TEMP: scratch, TMP: scratch, LANG: "C" },
182
- timeout: 15_000, maxBuffer: 4 * 1024 * 1024,
183
- });
184
- if (child.error) return { ok: false, error: `runtime-check-failed: ${child.error.message}` };
185
- const resultLine = child.stdout.split("\n").filter((line) => line.startsWith("__DSH_CLOSURE_RESULT__")).at(-1);
186
- if (!resultLine) return { ok: false, error: `runtime-check-no-result: exit=${child.status} ${child.stderr.trim()}` };
187
- const result = JSON.parse(resultLine.slice("__DSH_CLOSURE_RESULT__".length));
188
- if (child.status !== 0 && result.ok) return { ok: false, error: `runtime-check-exit-${child.status}` };
189
- return result;
190
- } finally {
191
- cleanupTemporary(scratch, parent);
192
- }
193
- }
194
-
195
- function inventory(packageRoot) {
196
- const root = realpathSync(packageRoot);
197
- const records = [];
198
- const visit = (dir, rel = "") => {
199
- for (const name of readdirSync(dir).sort()) {
200
- if (rel === "" && name === RUNTIME_COMPLETE_MARKER) continue;
201
- const full = join(dir, name);
202
- const child = rel ? `${rel}/${name}` : name;
203
- const st = lstatSync(full);
204
- if (st.isSymbolicLink()) {
205
- const real = realpathSync(full); // broken 链接也必须失败,不允许 skip。
206
- if (!contained(root, real)) throw new Error(`symlink-outside-package: ${child}`);
207
- // 只记录链接,不递归,可覆盖循环依赖;目标的文件另有独立记录。
208
- records.push([child, "link", readlinkSync(full)]);
209
- } else if (st.isDirectory()) {
210
- records.push([child, "dir"]);
211
- visit(full, child);
212
- } else if (st.isFile()) {
213
- records.push([child, "file", st.mode & 0o777, createHash("sha256").update(readFileSync(full)).digest("hex")]);
214
- } else throw new Error(`unsupported-package-entry: ${child}`);
215
- }
216
- };
217
- visit(root);
218
- const digest = createHash("sha256").update("dsh-runtime-closure/v1\n").update(JSON.stringify(records)).digest("hex");
219
- return { digest, entries: records.length };
220
- }
221
-
222
- // 载体排除的单一判定点(建图/冻结/冻结后校验/复用恢复共用):
223
- // - 通过身份校验 → 返回可跳过的别名;
224
- // - 根包声明了、作用域里也确实有候选,但候选身份不符/多义 → fail-closed,带稳定 reason;
225
- // - 未声明或未安装 → 正常(可选依赖缺失不阻塞闭包)。
226
- // 普通依赖自报 dsh.appCarrier 不会走到这里,也不会被豁免。
227
- function carrierExclusion(root) {
228
- const exclusion = appCarrierExclusion({ root });
229
- if (exclusion?.exclude === true && typeof exclusion.alias === "string") {
230
- return { ok: true, carrier: { alias: exclusion.alias } };
231
- }
232
- if (exclusion?.hard === true) return { ok: false, error: `app-carrier-invalid: ${exclusion.reason}` };
233
- return { ok: true, carrier: null };
234
- }
235
-
236
- /**
237
- * 无 marker 也可验证发行 tgz:全部声明依赖与 smoke 实际加载文件均在 packageRoot 内。
238
- * qrcode.toBuffer(PNG) 覆盖 lazy API;CJS + ESM require/import 均做 realpath containment。
239
- * 需要支持 module.registerHooks 的 Node(22.15+/23.5+);不支持时 fail-closed,不降为只核 CJS。
240
- * 根包显式声明且身份校验通过的 App 载体不进入闭包(数据依赖,不是运行依赖)。
241
- */
242
- export function verifyRuntimeClosure(packageRoot) {
243
- try {
244
- if (!statSync(join(packageRoot, "lib")).isDirectory()) throw new Error("package-lib-missing");
245
- inventory(packageRoot); // 任何树内外链/broken 链接均拒绝。
246
- const root = realpathSync(packageRoot);
247
- const exclusion = carrierExclusion(root);
248
- if (!exclusion.ok) return { ok: false, error: exclusion.error };
249
- return runWorker(root, "verify", exclusion.carrier);
250
- } catch (error) {
251
- return { ok: false, error: String(error.message ?? error) };
252
- }
253
- }
254
-
255
- /** digest、marker schema、完整加载图全过,才可以复用快照。旧的时间戳 marker 一律拒绝。 */
256
- export function verifyFrozenPackage(packageRoot) {
257
- try {
258
- const markerPath = join(packageRoot, RUNTIME_COMPLETE_MARKER);
259
- if (!lstatSync(markerPath).isFile()) throw new Error("snapshot-marker-not-file");
260
- const marker = JSON.parse(readFileSync(markerPath, "utf8"));
261
- if (marker.schema !== MARKER_SCHEMA || marker.algorithm !== "sha256" || !/^[a-f0-9]{64}$/.test(marker.digest ?? "")) {
262
- throw new Error("snapshot-marker-invalid");
263
- }
264
- const actual = inventory(packageRoot);
265
- if (marker.digest !== actual.digest || marker.entries !== actual.entries) throw new Error("snapshot-integrity-mismatch");
266
- const runtime = verifyRuntimeClosure(packageRoot);
267
- return runtime.ok ? { ...runtime, digest: actual.digest } : runtime;
268
- } catch (error) {
269
- return { ok: false, error: String(error.message ?? error) };
270
- }
271
- }
272
-
273
- export function frozenPackageReady(packageRoot) {
274
- return verifyFrozenPackage(packageRoot).ok;
275
- }
276
-
277
- function copyPayload(source, destination, copyFile, ancestors = new Set()) {
278
- const real = realpathSync(source); // 必需链接缺失必须 throw。
279
- const st = statSync(real);
280
- if (st.isFile()) {
281
- const result = copyFile(real, destination);
282
- if (result?.ok === false) throw new Error(result.error ?? "copy-file-failed");
283
- chmodSync(destination, st.mode & 0o777);
284
- return;
285
- }
286
- if (!st.isDirectory()) throw new Error(`unsupported-source-entry: ${source}`);
287
- if (ancestors.has(real)) throw new Error(`source-symlink-cycle: ${source}`);
288
- const next = new Set(ancestors).add(real);
289
- mkdirSync(destination, { recursive: true });
290
- for (const name of readdirSync(real).sort()) {
291
- // 不复制安装布局 / VCS 元数据;由真实解析的图重新构造 node_modules。
292
- if (name === "node_modules" || name === ".git") continue;
293
- copyPayload(join(real, name), join(destination, name), copyFile, next);
294
- }
295
- }
296
-
297
- /**
298
- * 同步、fail-closed、不可覆盖的快照发布。
299
- * copyFile(src,dest) 可注入真实拷贝故障;throw 或 {ok:false,error} 都拒绝,验证前无 .complete。
300
- * destination 必须尚不存在(损坏的旧目录由调用方拒绝使用,不能当可复用快照)。
301
- */
302
- export function freezePackage({ sourceRoot, destination, copyFile = copyFileSync } = {}) {
303
- let staging;
304
- let parent;
305
- try {
306
- const source = realpathSync(sourceRoot);
307
- const dest = canonicalDestination(resolve(destination));
308
- if (contained(source, dest) || contained(dest, source)) throw new Error("snapshot-source-destination-overlap");
309
- try { lstatSync(dest); throw new Error("snapshot-destination-exists"); }
310
- catch (error) { if (error.code !== "ENOENT") throw error; }
311
- // 先判定载体排除(根包声明 + 身份校验),再建图:载体在建图阶段就被跳过。
312
- const carrier = carrierExclusion(source);
313
- if (!carrier.ok) throw new Error(carrier.error);
314
- const graph = runWorker(source, "graph", carrier.carrier);
315
- if (!graph.ok) return graph;
316
- mkdirSync(dirname(dest), { recursive: true });
317
- parent = realpathSync(dirname(dest));
318
- staging = mkdtempSync(join(parent, ".dsh-runtime-snapshot-"));
319
- copyPayload(join(source, "package.json"), join(staging, "package.json"), copyFile);
320
- copyPayload(join(source, "lib"), join(staging, "lib"), copyFile);
321
- const targets = graph.nodes.map((_, id) => id === 0 ? staging : join(staging, "node_modules/.runtime-deps", String(id)));
322
- for (let id = 1; id < graph.nodes.length; id++) copyPayload(graph.nodes[id].source, targets[id], copyFile);
323
- for (let id = 0; id < graph.nodes.length; id++) {
324
- for (const [name, dependencyId] of Object.entries(graph.nodes[id].dependencies)) {
325
- const link = join(targets[id], "node_modules", name);
326
- mkdirSync(dirname(link), { recursive: true });
327
- symlinkSync(relative(dirname(link), targets[dependencyId]), link, "dir");
328
- }
329
- }
330
- const runtime = verifyRuntimeClosure(staging);
331
- if (!runtime.ok) throw new Error(runtime.error);
332
- const snapshot = inventory(staging);
333
- writeFileSync(join(staging, RUNTIME_COMPLETE_MARKER), JSON.stringify({ schema: MARKER_SCHEMA, algorithm: "sha256", ...snapshot }) + "\n", { flag: "wx" });
334
- // 同一父目录内 rename,一次发布已验证树。绝不先删已有目标。
335
- try { lstatSync(dest); throw new Error("snapshot-destination-exists"); }
336
- catch (error) { if (error.code !== "ENOENT") throw error; }
337
- renameSync(staging, dest);
338
- staging = undefined;
339
- return { ok: true, dir: dest, digest: snapshot.digest, carrierExcluded: carrier.carrier !== null };
340
- } catch (error) {
341
- return { ok: false, error: `snapshot-failed: ${error.message ?? error}` };
342
- } finally {
343
- if (staging) cleanupTemporary(staging, parent);
344
- }
345
- }