@synopackageland/cli 0.6.1 → 0.7.0

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 (578) hide show
  1. package/CHANGELOG.md +613 -0
  2. package/README.md +81 -89
  3. package/dist/add.js +39 -16
  4. package/dist/build-spk/delegate-scope.d.ts +6 -0
  5. package/dist/build-spk/delegate-scope.js +34 -0
  6. package/dist/build-spk/dsm-arch.d.ts +7 -1
  7. package/dist/build-spk/dsm-arch.js +24 -28
  8. package/dist/build-spk/dsm-platforms.json +39 -0
  9. package/dist/build-spk/dsm-version.d.ts +11 -0
  10. package/dist/build-spk/dsm-version.js +17 -0
  11. package/dist/build-spk/gateway.d.ts +1 -1
  12. package/dist/build-spk/gateway.js +2 -2
  13. package/dist/build-spk/generator.js +30 -5
  14. package/dist/build-spk/notices.js +4 -1
  15. package/dist/build-spk/resource-worker.js +3 -0
  16. package/dist/build-spk/wizard-messages.d.ts +66 -0
  17. package/dist/build-spk/wizard-messages.js +75 -9
  18. package/dist/cli.js +15 -7
  19. package/dist/contract/schema-validate.js +8 -0
  20. package/dist/contract/validate.js +30 -0
  21. package/dist/deploy/deploy.d.ts +1 -1
  22. package/dist/deploy/deploy.js +8 -0
  23. package/dist/deploy/plan.js +1 -1
  24. package/dist/deploy/spk-version.js +1 -1
  25. package/dist/error-registry.js +43 -15
  26. package/dist/host/dsm-client.d.ts +8 -4
  27. package/dist/host/dsm-client.js +10 -4
  28. package/dist/host/types.d.ts +2 -1
  29. package/dist/info.js +18 -2
  30. package/dist/init.d.ts +3 -0
  31. package/dist/init.js +49 -18
  32. package/dist/package-metadata.d.ts +21 -0
  33. package/dist/package-metadata.js +101 -0
  34. package/dist/portal/alias.d.ts +25 -0
  35. package/dist/portal/alias.js +48 -0
  36. package/dist/portal/package.d.ts +33 -0
  37. package/dist/portal/package.js +116 -0
  38. package/dist/runtime/delegate-contract.d.ts +79 -0
  39. package/dist/runtime/delegate-contract.js +27 -0
  40. package/dist/runtime/executor-build.d.ts +1 -1
  41. package/dist/runtime/executor-build.js +5 -4
  42. package/dist/runtime/executor-inspect.d.ts +3 -0
  43. package/dist/runtime/executor-inspect.js +11 -5
  44. package/dist/runtime/executor-policy.d.ts +12 -0
  45. package/dist/runtime/executor-policy.js +43 -3
  46. package/dist/runtime/executor-scope-task.d.ts +4 -0
  47. package/dist/runtime/executor-scope-task.js +21 -0
  48. package/dist/runtime/executor-tasks.d.ts +2 -1
  49. package/dist/runtime/executor-tasks.js +4 -4
  50. package/dist/skill.js +1 -0
  51. package/dist/standalone/api-permissions.d.ts +10 -0
  52. package/dist/standalone/api-permissions.js +19 -2
  53. package/dist/standalone/background.d.ts +16 -0
  54. package/dist/standalone/background.js +69 -2
  55. package/dist/standalone/binary.d.ts +1 -1
  56. package/dist/standalone/binary.js +22 -8
  57. package/dist/standalone/build.d.ts +1 -1
  58. package/dist/standalone/build.js +38 -8
  59. package/dist/standalone/daemon.d.ts +1 -1
  60. package/dist/standalone/daemon.js +2 -1
  61. package/dist/standalone/dsm-window.d.ts +3 -1
  62. package/dist/standalone/dsm-window.js +72 -15
  63. package/dist/standalone/inspect-spk.d.ts +8 -0
  64. package/dist/standalone/inspect-spk.js +105 -26
  65. package/dist/standalone/lifecycle.js +86 -8
  66. package/dist/standalone/manifest.d.ts +13 -0
  67. package/dist/standalone/manifest.js +57 -8
  68. package/dist/standalone/native-authorization-window.d.ts +1 -1
  69. package/dist/standalone/native-authorization-window.js +2 -0
  70. package/dist/standalone/native-layout.d.ts +3 -0
  71. package/dist/standalone/native-layout.js +37 -5
  72. package/dist/standalone/portal-window.d.ts +21 -0
  73. package/dist/standalone/portal-window.js +155 -0
  74. package/dist/standalone/portal.d.ts +4 -0
  75. package/dist/standalone/portal.js +4 -0
  76. package/dist/standalone/resource.d.ts +5 -3
  77. package/dist/standalone/resource.js +6 -3
  78. package/dist/standalone/root-window.js +62 -15
  79. package/dist/standalone/spk.d.ts +1 -1
  80. package/dist/standalone/spk.js +25 -7
  81. package/dist/templates/_daemon/main_test.go +61 -8
  82. package/dist/templates/_executor/README.md +127 -16
  83. package/dist/templates/_executor/anchor.go +146 -0
  84. package/dist/templates/_executor/anchor_test.go +126 -0
  85. package/dist/templates/_executor/apppriv_review_test.go +1 -1
  86. package/dist/templates/_executor/cancel_contract_test.go +236 -0
  87. package/dist/templates/_executor/copy.go +64 -0
  88. package/dist/templates/_executor/copy_test.go +88 -0
  89. package/dist/templates/_executor/dsm.go +23 -10
  90. package/dist/templates/_executor/dsm_test.go +95 -1
  91. package/dist/templates/_executor/executor.go +260 -49
  92. package/dist/templates/_executor/executor_test.go +36 -6
  93. package/dist/templates/_executor/identity.go +27 -1
  94. package/dist/templates/_executor/identity_kernel_linux_test.go +60 -0
  95. package/dist/templates/_executor/identity_linux.go +82 -0
  96. package/dist/templates/_executor/identity_security_test.go +43 -0
  97. package/dist/templates/_executor/internal/lifecycle/lifecycle.go +5 -0
  98. package/dist/templates/_executor/internal/lifecycle/lifecycle_test.go +61 -3
  99. package/dist/templates/_executor/internal/taskscript/boot.sh +9 -9
  100. package/dist/templates/_executor/internal/taskscript/cmd/main.go +2 -2
  101. package/dist/templates/_executor/internal/taskscript/control.sh +15 -10
  102. package/dist/templates/_executor/internal/taskscript/tasks.go +5 -3
  103. package/dist/templates/_executor/lifecycle_test.go +11 -0
  104. package/dist/templates/_executor/link_linux.go +28 -0
  105. package/dist/templates/_executor/long_running_test.go +530 -0
  106. package/dist/templates/_executor/main.go +72 -20
  107. package/dist/templates/_executor/move_linux.go +490 -0
  108. package/dist/templates/_executor/move_linux_test.go +460 -0
  109. package/dist/templates/_executor/move_validation.go +46 -0
  110. package/dist/templates/_executor/move_validation_test.go +187 -0
  111. package/dist/templates/_executor/operations.go +926 -0
  112. package/dist/templates/_executor/platform_linux.go +24 -173
  113. package/dist/templates/_executor/platform_linux_test.go +9 -65
  114. package/dist/templates/_executor/platform_other.go +30 -4
  115. package/dist/templates/_executor/process.go +3 -0
  116. package/dist/templates/_executor/records.go +242 -0
  117. package/dist/templates/_executor/scope_cancel_contract_test.go +91 -0
  118. package/dist/templates/_executor/scope_client.go +37 -0
  119. package/dist/templates/_executor/scope_contract.go +12 -0
  120. package/dist/templates/_executor/selected_scope.go +146 -0
  121. package/dist/templates/_executor/selected_scope_test.go +186 -0
  122. package/dist/templates/_executor/status_disabled_test.go +33 -0
  123. package/dist/templates/_executor/store.go +4 -3
  124. package/dist/templates/_executor/supervise.go +109 -0
  125. package/dist/templates/_executor/supervise_test.go +130 -0
  126. package/dist/templates/_executor/task_race_review_test.go +1 -1
  127. package/dist/templates/_executor/tasks.go +7 -8
  128. package/dist/templates/_executor/tasks_review_test.go +1 -1
  129. package/dist/templates/_executor/tasks_test.go +4 -4
  130. package/dist/templates/_executor/watch.go +8 -0
  131. package/dist/templates/_executor/watch_test.go +23 -2
  132. package/dist/templates/_executor/write_review_linux_test.go +4 -12
  133. package/dist/templates/_shared/AGENTS.md +129 -0
  134. package/dist/templates/binary/dsm-package.yaml +24 -0
  135. package/dist/templates/binary/i18n/messages/de.json +4 -0
  136. package/dist/templates/binary/i18n/messages/en.json +4 -0
  137. package/dist/templates/binary/i18n/messages/es.json +4 -0
  138. package/dist/templates/binary/i18n/messages/fr.json +4 -0
  139. package/dist/templates/binary/i18n/messages/it.json +4 -0
  140. package/dist/templates/binary/i18n/messages/ja.json +4 -0
  141. package/dist/templates/binary/i18n/messages/ko.json +4 -0
  142. package/dist/templates/binary/i18n/messages/zh-CN.json +4 -0
  143. package/dist/templates/binary/i18n/messages/zh-TW.json +4 -0
  144. package/dist/templates/binary/ui/app.js +19 -4
  145. package/dist/templates/native-authorization/AuthorizationGate.vue +5 -3
  146. package/dist/templates/native-authorization/i18n/de.json +1 -0
  147. package/dist/templates/native-authorization/i18n/en.json +1 -0
  148. package/dist/templates/native-authorization/i18n/es.json +2 -1
  149. package/dist/templates/native-authorization/i18n/fr.json +1 -0
  150. package/dist/templates/native-authorization/i18n/it.json +11 -10
  151. package/dist/templates/native-authorization/i18n/ja.json +1 -0
  152. package/dist/templates/native-authorization/i18n/ko.json +10 -9
  153. package/dist/templates/native-authorization/i18n/zh-CN.json +6 -5
  154. package/dist/templates/native-authorization/i18n/zh-TW.json +6 -5
  155. package/dist/templates/native-go/README.md +43 -10
  156. package/dist/templates/native-go/backend/cmd/app/main.go +27 -7
  157. package/dist/templates/native-go/backend/internal/api/entry_test.go +47 -0
  158. package/dist/templates/native-go/backend/internal/api/permissions_test.go +3 -3
  159. package/dist/templates/native-go/backend/internal/api/registry.go +11 -0
  160. package/dist/templates/native-go/backend/internal/api/sdk-permissions.json +14 -1
  161. package/dist/templates/native-go/backend/internal/api/server.go +18 -5
  162. package/dist/templates/native-go/backend/internal/app/delegate_config.go +57 -0
  163. package/dist/templates/native-go/backend/internal/app/delegate_config_test.go +28 -0
  164. package/dist/templates/native-go/backend/internal/app/portal.go +30 -0
  165. package/dist/templates/native-go/backend/internal/app/portal_test.go +55 -0
  166. package/dist/templates/native-go/backend/internal/app/routes.go +8 -2
  167. package/dist/templates/native-go/backend/internal/background/runner.go +1 -1
  168. package/dist/templates/native-go/backend/internal/background/store.go +16 -14
  169. package/dist/templates/native-go/backend/internal/dsm/admin.go +8 -2
  170. package/dist/templates/native-go/backend/internal/dsm/admin_linux.go +43 -13
  171. package/dist/templates/native-go/backend/internal/dsm/admin_linux_test.go +56 -0
  172. package/dist/templates/native-go/backend/internal/dsm/admin_other.go +4 -1
  173. package/dist/templates/native-go/backend/internal/dsm/apppriv.go +30 -0
  174. package/dist/templates/native-go/backend/internal/dsm/bridge_file.go +75 -0
  175. package/dist/templates/native-go/backend/internal/dsm/bridge_file_test.go +57 -0
  176. package/dist/templates/native-go/backend/internal/dsm/entry.go +177 -0
  177. package/dist/templates/native-go/backend/internal/dsm/entry_test.go +315 -0
  178. package/dist/templates/native-go/backend/internal/dsm/native/admin-armv7.elf +0 -0
  179. package/dist/templates/native-go/backend/internal/dsm/native/apppriv-aarch64.elf +0 -0
  180. package/dist/templates/native-go/backend/internal/dsm/native/apppriv-armv7.elf +0 -0
  181. package/dist/templates/native-go/backend/internal/dsm/native/apppriv-x86_64.elf +0 -0
  182. package/dist/templates/native-go/backend/internal/dsm/native/apppriv_check.c +33 -0
  183. package/dist/templates/native-go/backend/internal/dsm/native/build.sh +13 -4
  184. package/dist/templates/native-go/backend/internal/dsm/native/provenance.json +9 -3
  185. package/dist/templates/native-go/backend/internal/filestation/check_permission.go +71 -0
  186. package/dist/templates/native-go/backend/internal/filestation/check_permission_test.go +77 -0
  187. package/dist/templates/native-go/backend/internal/filestation/sdk_policy_test.go +39 -0
  188. package/dist/templates/native-go/backend/internal/filestation/service.go +1 -1
  189. package/dist/templates/native-go/backend/internal/portal/portal.go +138 -0
  190. package/dist/templates/native-go/backend/internal/portal/portal_test.go +89 -0
  191. package/dist/templates/native-go/dsm-package.yaml +20 -1
  192. package/dist/templates/native-go/i18n/glossary.json +2 -1
  193. package/dist/templates/native-go/i18n/messages/de.json +1 -0
  194. package/dist/templates/native-go/i18n/messages/en.json +1 -0
  195. package/dist/templates/native-go/i18n/messages/es.json +1 -0
  196. package/dist/templates/native-go/i18n/messages/fr.json +1 -0
  197. package/dist/templates/native-go/i18n/messages/it.json +1 -0
  198. package/dist/templates/native-go/i18n/messages/ja.json +1 -0
  199. package/dist/templates/native-go/i18n/messages/ko.json +1 -0
  200. package/dist/templates/native-go/i18n/messages/zh-CN.json +1 -0
  201. package/dist/templates/native-go/i18n/messages/zh-TW.json +1 -0
  202. package/dist/templates/native-go/ui/app.css +10 -0
  203. package/dist/templates/native-go/ui/app.js +11 -1
  204. package/dist/templates/native-go-delegate/DELEGATE.md +126 -5
  205. package/dist/templates/native-go-delegate/backend/cmd/background-worker/main.go +7 -0
  206. package/dist/templates/native-go-delegate/backend/internal/app/delegate.go +11 -1
  207. package/dist/templates/native-go-delegate/backend/internal/app/delegate_routes_test.go +14 -0
  208. package/dist/templates/native-go-delegate/backend/internal/background/delegated_moves.go +270 -0
  209. package/dist/templates/native-go-delegate/backend/internal/delegate/admission_recovery_test.go +410 -0
  210. package/dist/templates/native-go-delegate/backend/internal/delegate/cancel_contract_test.go +95 -0
  211. package/dist/templates/native-go-delegate/backend/internal/delegate/client.go +8 -0
  212. package/dist/templates/native-go-delegate/backend/internal/delegate/destination.go +110 -0
  213. package/dist/templates/native-go-delegate/backend/internal/delegate/destination_state.go +117 -0
  214. package/dist/templates/native-go-delegate/backend/internal/delegate/destination_state_test.go +60 -0
  215. package/dist/templates/native-go-delegate/backend/internal/delegate/destination_test.go +116 -0
  216. package/dist/templates/native-go-delegate/backend/internal/delegate/example.go +81 -0
  217. package/dist/templates/native-go-delegate/backend/internal/delegate/example_test.go +91 -0
  218. package/dist/templates/native-go-delegate/backend/internal/delegate/executor_contract_test.go +511 -0
  219. package/dist/templates/native-go-delegate/backend/internal/delegate/files.go +1 -1
  220. package/dist/templates/native-go-delegate/backend/internal/delegate/long_running_routes.go +92 -0
  221. package/dist/templates/native-go-delegate/backend/internal/delegate/long_running_routes_test.go +99 -0
  222. package/dist/templates/native-go-delegate/backend/internal/delegate/move.go +348 -0
  223. package/dist/templates/native-go-delegate/backend/internal/delegate/move_test.go +351 -0
  224. package/dist/templates/native-go-delegate/backend/internal/delegate/operations_list.go +65 -0
  225. package/dist/templates/native-go-delegate/backend/internal/delegate/operations_list_test.go +114 -0
  226. package/dist/templates/native-go-delegate/backend/internal/delegate/retry_test.go +1 -1
  227. package/dist/templates/native-go-delegate/backend/internal/delegate/service.go +58 -17
  228. package/dist/templates/native-go-delegate/backend/internal/delegate/staging.go +121 -0
  229. package/dist/templates/native-go-delegate/backend/internal/delegate/status_disabled_test.go +51 -0
  230. package/dist/templates/native-go-delegate/backend/internal/delegate/testdata/executor_contract.txt +112 -0
  231. package/dist/templates/native-go-delegate/backend/internal/delegate/transient_contract_test.go +137 -0
  232. package/dist/templates/native-go-delegate/backend/internal/filestation/files.go +1 -31
  233. package/dist/templates/native-go-delegate/frontend/src/CapabilityPanels.vue +30 -3
  234. package/dist/templates/native-go-delegate/frontend/src/delegate/BackgroundSavePanel.vue +113 -0
  235. package/dist/templates/native-go-delegate/frontend/src/delegate/copy.ts +5 -6
  236. package/dist/templates/native-go-delegate/frontend/test/background-save.test.ts +121 -0
  237. package/dist/templates/native-go-delegate/frontend/test/scope-settings.test.ts +49 -0
  238. package/dist/templates/native-go-delegate/test.sh +1 -0
  239. package/dist/templates/native-go-jobs/frontend/src/background/i18n/de.json +2 -2
  240. package/dist/templates/native-go-jobs/frontend/src/background/i18n/en.json +2 -2
  241. package/dist/templates/native-go-jobs/frontend/src/background/i18n/es.json +12 -12
  242. package/dist/templates/native-go-jobs/frontend/src/background/i18n/fr.json +4 -4
  243. package/dist/templates/native-go-jobs/frontend/src/background/i18n/it.json +17 -17
  244. package/dist/templates/native-go-jobs/frontend/src/background/i18n/ja.json +7 -7
  245. package/dist/templates/native-go-jobs/frontend/src/background/i18n/ko.json +15 -15
  246. package/dist/templates/native-go-jobs/frontend/src/background/i18n/zh-CN.json +3 -3
  247. package/dist/templates/native-go-jobs/frontend/src/background/i18n/zh-TW.json +2 -2
  248. package/dist/templates/native-go-jobs/frontend/test/background.test.ts +25 -28
  249. package/dist/templates/native-go-vue/README.md +4 -0
  250. package/dist/templates/native-go-vue/frontend/package-lock.json +4 -4
  251. package/dist/templates/native-go-vue/frontend/package.json +1 -1
  252. package/dist/templates/native-go-vue/frontend/src/App.vue +18 -13
  253. package/dist/templates/native-go-vue/frontend/src/context.ts +31 -0
  254. package/dist/templates/native-go-vue/frontend/src/delegate/ExecutorSettings.vue +52 -8
  255. package/dist/templates/native-go-vue/frontend/src/delegate/catalogs.ts +11 -0
  256. package/dist/templates/native-go-vue/frontend/src/delegate/copy.test.ts +12 -0
  257. package/dist/templates/native-go-vue/frontend/src/delegate/copy.ts +4 -4
  258. package/dist/templates/native-go-vue/frontend/src/delegate/executor-setup.ts +4 -2
  259. package/dist/templates/native-go-vue/frontend/src/delegate/facade.test.ts +100 -0
  260. package/dist/templates/native-go-vue/frontend/src/delegate/facade.ts +85 -0
  261. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/de.json +46 -0
  262. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/en.json +12 -2
  263. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/es.json +46 -0
  264. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/fr.json +46 -0
  265. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/it.json +46 -0
  266. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/ja.json +46 -0
  267. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/ko.json +46 -0
  268. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/de.json +42 -0
  269. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/en.json +42 -0
  270. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/es.json +42 -0
  271. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/fr.json +42 -0
  272. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/it.json +42 -0
  273. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/ja.json +42 -0
  274. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/ko.json +42 -0
  275. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-CN.json +42 -0
  276. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-TW.json +42 -0
  277. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/zh-CN.json +46 -0
  278. package/dist/templates/native-go-vue/frontend/src/delegate/i18n/zh-TW.json +15 -5
  279. package/dist/templates/native-go-vue/frontend/src/delegate/long-running.ts +57 -0
  280. package/dist/templates/native-go-vue/frontend/src/delegate/save-copy.test.ts +12 -0
  281. package/dist/templates/native-go-vue/frontend/src/delegate/save-copy.ts +24 -0
  282. package/dist/templates/native-go-vue/frontend/src/delegate/scoped-picker.test.ts +12 -0
  283. package/dist/templates/native-go-vue/frontend/src/delegate/scoped-picker.ts +21 -0
  284. package/dist/templates/native-go-vue/frontend/src/files/files.test.ts +7 -0
  285. package/dist/templates/native-go-vue/frontend/src/i18n/de.json +20 -0
  286. package/dist/templates/native-go-vue/frontend/src/i18n/en.json +20 -0
  287. package/dist/templates/native-go-vue/frontend/src/i18n/es.json +20 -0
  288. package/dist/templates/native-go-vue/frontend/src/i18n/fr.json +20 -0
  289. package/dist/templates/native-go-vue/frontend/src/i18n/it.json +20 -0
  290. package/dist/templates/native-go-vue/frontend/src/i18n/ja.json +20 -0
  291. package/dist/templates/native-go-vue/frontend/src/i18n/ko.json +20 -0
  292. package/dist/templates/native-go-vue/frontend/src/i18n/zh-CN.json +20 -0
  293. package/dist/templates/native-go-vue/frontend/src/i18n/zh-TW.json +20 -0
  294. package/dist/templates/native-go-vue/frontend/src/i18n.ts +13 -0
  295. package/dist/templates/native-go-vue/frontend/src/identity/LoginPage.vue +16 -14
  296. package/dist/templates/native-go-vue/frontend/src/identity/i18n/de.json +27 -0
  297. package/dist/templates/native-go-vue/frontend/src/identity/i18n/en.json +27 -0
  298. package/dist/templates/native-go-vue/frontend/src/identity/i18n/es.json +27 -0
  299. package/dist/templates/native-go-vue/frontend/src/identity/i18n/fr.json +27 -0
  300. package/dist/templates/native-go-vue/frontend/src/identity/i18n/it.json +27 -0
  301. package/dist/templates/native-go-vue/frontend/src/identity/i18n/ja.json +27 -0
  302. package/dist/templates/native-go-vue/frontend/src/identity/i18n/ko.json +27 -0
  303. package/dist/templates/native-go-vue/frontend/src/identity/i18n/zh-CN.json +27 -0
  304. package/dist/templates/native-go-vue/frontend/src/identity/i18n/zh-TW.json +27 -0
  305. package/dist/templates/native-go-vue/frontend/src/identity/i18n.ts +14 -0
  306. package/dist/templates/native-go-vue/frontend/src/identity/session.test.ts +4 -0
  307. package/dist/templates/native-go-vue/frontend/src/identity/session.ts +9 -8
  308. package/dist/templates/native-go-vue/frontend/src/nas/NasPicker.vue +31 -12
  309. package/dist/templates/native-go-vue/frontend/src/nas/destination.test.ts +48 -0
  310. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/de.json +1 -1
  311. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/en.json +1 -1
  312. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/es.json +10 -10
  313. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/fr.json +1 -1
  314. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/it.json +12 -12
  315. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/ja.json +15 -15
  316. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/ko.json +12 -12
  317. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/zh-CN.json +1 -1
  318. package/dist/templates/native-go-vue/frontend/src/nas/i18n/files/zh-TW.json +1 -1
  319. package/dist/templates/native-go-vue/frontend/src/portal/PortalSettings.vue +39 -0
  320. package/dist/templates/native-go-vue/frontend/src/shell/i18n/fr.json +1 -1
  321. package/dist/templates/native-go-vue/frontend/src/shell/i18n/ja.json +1 -1
  322. package/dist/templates/native-go-vue/frontend/test/apps.test.ts +14 -0
  323. package/dist/templates/native-go-vue/frontend/test/portal-settings.test.ts +71 -0
  324. package/dist/templates/native-go-vue/frontend/test/shell-i18n.test.ts +50 -0
  325. package/package.json +5 -1
  326. package/schema/package-metadata.schema.json +101 -0
  327. package/schema/synology-app.schema.json +112 -0
  328. package/skills/SKILL.md +106 -4
  329. package/skills/reference/create.md +101 -2
  330. package/skills/reference/deploy.md +2 -2
  331. package/skills/reference/files.md +113 -2
  332. package/skills/reference/identity.md +70 -1
  333. package/skills/reference/portal.md +17 -0
  334. package/skills/reference/ui-vue.md +12 -0
  335. package/templates/_daemon/main_test.go +61 -8
  336. package/templates/_executor/README.md +127 -16
  337. package/templates/_executor/anchor.go +146 -0
  338. package/templates/_executor/anchor_test.go +126 -0
  339. package/templates/_executor/apppriv_review_test.go +1 -1
  340. package/templates/_executor/cancel_contract_test.go +236 -0
  341. package/templates/_executor/copy.go +64 -0
  342. package/templates/_executor/copy_test.go +88 -0
  343. package/templates/_executor/dsm.go +23 -10
  344. package/templates/_executor/dsm_test.go +95 -1
  345. package/templates/_executor/executor.go +260 -49
  346. package/templates/_executor/executor_test.go +36 -6
  347. package/templates/_executor/identity.go +27 -1
  348. package/templates/_executor/identity_kernel_linux_test.go +60 -0
  349. package/templates/_executor/identity_linux.go +82 -0
  350. package/templates/_executor/identity_security_test.go +43 -0
  351. package/templates/_executor/internal/lifecycle/lifecycle.go +5 -0
  352. package/templates/_executor/internal/lifecycle/lifecycle_test.go +61 -3
  353. package/templates/_executor/internal/taskscript/boot.sh +9 -9
  354. package/templates/_executor/internal/taskscript/cmd/main.go +2 -2
  355. package/templates/_executor/internal/taskscript/control.sh +15 -10
  356. package/templates/_executor/internal/taskscript/tasks.go +5 -3
  357. package/templates/_executor/lifecycle_test.go +11 -0
  358. package/templates/_executor/link_linux.go +28 -0
  359. package/templates/_executor/long_running_test.go +530 -0
  360. package/templates/_executor/main.go +72 -20
  361. package/templates/_executor/move_linux.go +490 -0
  362. package/templates/_executor/move_linux_test.go +460 -0
  363. package/templates/_executor/move_validation.go +46 -0
  364. package/templates/_executor/move_validation_test.go +187 -0
  365. package/templates/_executor/operations.go +926 -0
  366. package/templates/_executor/platform_linux.go +24 -173
  367. package/templates/_executor/platform_linux_test.go +9 -65
  368. package/templates/_executor/platform_other.go +30 -4
  369. package/templates/_executor/process.go +3 -0
  370. package/templates/_executor/records.go +242 -0
  371. package/templates/_executor/scope_cancel_contract_test.go +91 -0
  372. package/templates/_executor/scope_client.go +37 -0
  373. package/templates/_executor/scope_contract.go +12 -0
  374. package/templates/_executor/selected_scope.go +146 -0
  375. package/templates/_executor/selected_scope_test.go +186 -0
  376. package/templates/_executor/status_disabled_test.go +33 -0
  377. package/templates/_executor/store.go +4 -3
  378. package/templates/_executor/supervise.go +109 -0
  379. package/templates/_executor/supervise_test.go +130 -0
  380. package/templates/_executor/task_race_review_test.go +1 -1
  381. package/templates/_executor/tasks.go +7 -8
  382. package/templates/_executor/tasks_review_test.go +1 -1
  383. package/templates/_executor/tasks_test.go +4 -4
  384. package/templates/_executor/watch.go +8 -0
  385. package/templates/_executor/watch_test.go +23 -2
  386. package/templates/_executor/write_review_linux_test.go +4 -12
  387. package/templates/_shared/AGENTS.md +129 -0
  388. package/templates/binary/dsm-package.yaml +24 -0
  389. package/templates/binary/i18n/messages/de.json +4 -0
  390. package/templates/binary/i18n/messages/en.json +4 -0
  391. package/templates/binary/i18n/messages/es.json +4 -0
  392. package/templates/binary/i18n/messages/fr.json +4 -0
  393. package/templates/binary/i18n/messages/it.json +4 -0
  394. package/templates/binary/i18n/messages/ja.json +4 -0
  395. package/templates/binary/i18n/messages/ko.json +4 -0
  396. package/templates/binary/i18n/messages/zh-CN.json +4 -0
  397. package/templates/binary/i18n/messages/zh-TW.json +4 -0
  398. package/templates/binary/ui/app.js +19 -4
  399. package/templates/native-authorization/AuthorizationGate.vue +5 -3
  400. package/templates/native-authorization/i18n/de.json +1 -0
  401. package/templates/native-authorization/i18n/en.json +1 -0
  402. package/templates/native-authorization/i18n/es.json +2 -1
  403. package/templates/native-authorization/i18n/fr.json +1 -0
  404. package/templates/native-authorization/i18n/it.json +11 -10
  405. package/templates/native-authorization/i18n/ja.json +1 -0
  406. package/templates/native-authorization/i18n/ko.json +10 -9
  407. package/templates/native-authorization/i18n/zh-CN.json +6 -5
  408. package/templates/native-authorization/i18n/zh-TW.json +6 -5
  409. package/templates/native-go/README.md +43 -10
  410. package/templates/native-go/backend/cmd/app/main.go +27 -7
  411. package/templates/native-go/backend/internal/api/entry_test.go +47 -0
  412. package/templates/native-go/backend/internal/api/permissions_test.go +3 -3
  413. package/templates/native-go/backend/internal/api/registry.go +11 -0
  414. package/templates/native-go/backend/internal/api/sdk-permissions.json +14 -1
  415. package/templates/native-go/backend/internal/api/server.go +18 -5
  416. package/templates/native-go/backend/internal/app/delegate_config.go +57 -0
  417. package/templates/native-go/backend/internal/app/delegate_config_test.go +28 -0
  418. package/templates/native-go/backend/internal/app/portal.go +30 -0
  419. package/templates/native-go/backend/internal/app/portal_test.go +55 -0
  420. package/templates/native-go/backend/internal/app/routes.go +8 -2
  421. package/templates/native-go/backend/internal/background/runner.go +1 -1
  422. package/templates/native-go/backend/internal/background/store.go +16 -14
  423. package/templates/native-go/backend/internal/dsm/admin.go +8 -2
  424. package/templates/native-go/backend/internal/dsm/admin_linux.go +43 -13
  425. package/templates/native-go/backend/internal/dsm/admin_linux_test.go +56 -0
  426. package/templates/native-go/backend/internal/dsm/admin_other.go +4 -1
  427. package/templates/native-go/backend/internal/dsm/apppriv.go +30 -0
  428. package/templates/native-go/backend/internal/dsm/bridge_file.go +75 -0
  429. package/templates/native-go/backend/internal/dsm/bridge_file_test.go +57 -0
  430. package/templates/native-go/backend/internal/dsm/entry.go +177 -0
  431. package/templates/native-go/backend/internal/dsm/entry_test.go +315 -0
  432. package/templates/native-go/backend/internal/dsm/native/admin-armv7.elf +0 -0
  433. package/templates/native-go/backend/internal/dsm/native/apppriv-aarch64.elf +0 -0
  434. package/templates/native-go/backend/internal/dsm/native/apppriv-armv7.elf +0 -0
  435. package/templates/native-go/backend/internal/dsm/native/apppriv-x86_64.elf +0 -0
  436. package/templates/native-go/backend/internal/dsm/native/apppriv_check.c +33 -0
  437. package/templates/native-go/backend/internal/dsm/native/build.sh +13 -4
  438. package/templates/native-go/backend/internal/dsm/native/provenance.json +9 -3
  439. package/templates/native-go/backend/internal/filestation/check_permission.go +71 -0
  440. package/templates/native-go/backend/internal/filestation/check_permission_test.go +77 -0
  441. package/templates/native-go/backend/internal/filestation/sdk_policy_test.go +39 -0
  442. package/templates/native-go/backend/internal/filestation/service.go +1 -1
  443. package/templates/native-go/backend/internal/portal/portal.go +138 -0
  444. package/templates/native-go/backend/internal/portal/portal_test.go +89 -0
  445. package/templates/native-go/dsm-package.yaml +20 -1
  446. package/templates/native-go/i18n/glossary.json +2 -1
  447. package/templates/native-go/i18n/messages/de.json +1 -0
  448. package/templates/native-go/i18n/messages/en.json +1 -0
  449. package/templates/native-go/i18n/messages/es.json +1 -0
  450. package/templates/native-go/i18n/messages/fr.json +1 -0
  451. package/templates/native-go/i18n/messages/it.json +1 -0
  452. package/templates/native-go/i18n/messages/ja.json +1 -0
  453. package/templates/native-go/i18n/messages/ko.json +1 -0
  454. package/templates/native-go/i18n/messages/zh-CN.json +1 -0
  455. package/templates/native-go/i18n/messages/zh-TW.json +1 -0
  456. package/templates/native-go/ui/app.css +10 -0
  457. package/templates/native-go/ui/app.js +11 -1
  458. package/templates/native-go-delegate/DELEGATE.md +126 -5
  459. package/templates/native-go-delegate/backend/cmd/background-worker/main.go +7 -0
  460. package/templates/native-go-delegate/backend/internal/app/delegate.go +11 -1
  461. package/templates/native-go-delegate/backend/internal/app/delegate_routes_test.go +14 -0
  462. package/templates/native-go-delegate/backend/internal/background/delegated_moves.go +270 -0
  463. package/templates/native-go-delegate/backend/internal/delegate/admission_recovery_test.go +410 -0
  464. package/templates/native-go-delegate/backend/internal/delegate/cancel_contract_test.go +95 -0
  465. package/templates/native-go-delegate/backend/internal/delegate/client.go +8 -0
  466. package/templates/native-go-delegate/backend/internal/delegate/destination.go +110 -0
  467. package/templates/native-go-delegate/backend/internal/delegate/destination_state.go +117 -0
  468. package/templates/native-go-delegate/backend/internal/delegate/destination_state_test.go +60 -0
  469. package/templates/native-go-delegate/backend/internal/delegate/destination_test.go +116 -0
  470. package/templates/native-go-delegate/backend/internal/delegate/example.go +81 -0
  471. package/templates/native-go-delegate/backend/internal/delegate/example_test.go +91 -0
  472. package/templates/native-go-delegate/backend/internal/delegate/executor_contract_test.go +511 -0
  473. package/templates/native-go-delegate/backend/internal/delegate/files.go +1 -1
  474. package/templates/native-go-delegate/backend/internal/delegate/long_running_routes.go +92 -0
  475. package/templates/native-go-delegate/backend/internal/delegate/long_running_routes_test.go +99 -0
  476. package/templates/native-go-delegate/backend/internal/delegate/move.go +348 -0
  477. package/templates/native-go-delegate/backend/internal/delegate/move_test.go +351 -0
  478. package/templates/native-go-delegate/backend/internal/delegate/operations_list.go +65 -0
  479. package/templates/native-go-delegate/backend/internal/delegate/operations_list_test.go +114 -0
  480. package/templates/native-go-delegate/backend/internal/delegate/retry_test.go +1 -1
  481. package/templates/native-go-delegate/backend/internal/delegate/service.go +58 -17
  482. package/templates/native-go-delegate/backend/internal/delegate/staging.go +121 -0
  483. package/templates/native-go-delegate/backend/internal/delegate/status_disabled_test.go +51 -0
  484. package/templates/native-go-delegate/backend/internal/delegate/testdata/executor_contract.txt +112 -0
  485. package/templates/native-go-delegate/backend/internal/delegate/transient_contract_test.go +137 -0
  486. package/templates/native-go-delegate/backend/internal/filestation/files.go +1 -31
  487. package/templates/native-go-delegate/frontend/src/CapabilityPanels.vue +30 -3
  488. package/templates/native-go-delegate/frontend/src/delegate/BackgroundSavePanel.vue +113 -0
  489. package/templates/native-go-delegate/frontend/src/delegate/copy.ts +5 -6
  490. package/templates/native-go-delegate/frontend/test/background-save.test.ts +121 -0
  491. package/templates/native-go-delegate/frontend/test/scope-settings.test.ts +49 -0
  492. package/templates/native-go-delegate/test.sh +1 -0
  493. package/templates/native-go-jobs/frontend/src/background/i18n/de.json +2 -2
  494. package/templates/native-go-jobs/frontend/src/background/i18n/en.json +2 -2
  495. package/templates/native-go-jobs/frontend/src/background/i18n/es.json +12 -12
  496. package/templates/native-go-jobs/frontend/src/background/i18n/fr.json +4 -4
  497. package/templates/native-go-jobs/frontend/src/background/i18n/it.json +17 -17
  498. package/templates/native-go-jobs/frontend/src/background/i18n/ja.json +7 -7
  499. package/templates/native-go-jobs/frontend/src/background/i18n/ko.json +15 -15
  500. package/templates/native-go-jobs/frontend/src/background/i18n/zh-CN.json +3 -3
  501. package/templates/native-go-jobs/frontend/src/background/i18n/zh-TW.json +2 -2
  502. package/templates/native-go-jobs/frontend/test/background.test.ts +25 -28
  503. package/templates/native-go-vue/README.md +4 -0
  504. package/templates/native-go-vue/frontend/package-lock.json +4 -4
  505. package/templates/native-go-vue/frontend/package.json +1 -1
  506. package/templates/native-go-vue/frontend/src/App.vue +18 -13
  507. package/templates/native-go-vue/frontend/src/context.ts +31 -0
  508. package/templates/native-go-vue/frontend/src/delegate/ExecutorSettings.vue +52 -8
  509. package/templates/native-go-vue/frontend/src/delegate/catalogs.ts +11 -0
  510. package/templates/native-go-vue/frontend/src/delegate/copy.test.ts +12 -0
  511. package/templates/native-go-vue/frontend/src/delegate/copy.ts +4 -4
  512. package/templates/native-go-vue/frontend/src/delegate/executor-setup.ts +4 -2
  513. package/templates/native-go-vue/frontend/src/delegate/facade.test.ts +100 -0
  514. package/templates/native-go-vue/frontend/src/delegate/facade.ts +85 -0
  515. package/templates/native-go-vue/frontend/src/delegate/i18n/de.json +46 -0
  516. package/templates/native-go-vue/frontend/src/delegate/i18n/en.json +12 -2
  517. package/templates/native-go-vue/frontend/src/delegate/i18n/es.json +46 -0
  518. package/templates/native-go-vue/frontend/src/delegate/i18n/fr.json +46 -0
  519. package/templates/native-go-vue/frontend/src/delegate/i18n/it.json +46 -0
  520. package/templates/native-go-vue/frontend/src/delegate/i18n/ja.json +46 -0
  521. package/templates/native-go-vue/frontend/src/delegate/i18n/ko.json +46 -0
  522. package/templates/native-go-vue/frontend/src/delegate/i18n/save/de.json +42 -0
  523. package/templates/native-go-vue/frontend/src/delegate/i18n/save/en.json +42 -0
  524. package/templates/native-go-vue/frontend/src/delegate/i18n/save/es.json +42 -0
  525. package/templates/native-go-vue/frontend/src/delegate/i18n/save/fr.json +42 -0
  526. package/templates/native-go-vue/frontend/src/delegate/i18n/save/it.json +42 -0
  527. package/templates/native-go-vue/frontend/src/delegate/i18n/save/ja.json +42 -0
  528. package/templates/native-go-vue/frontend/src/delegate/i18n/save/ko.json +42 -0
  529. package/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-CN.json +42 -0
  530. package/templates/native-go-vue/frontend/src/delegate/i18n/save/zh-TW.json +42 -0
  531. package/templates/native-go-vue/frontend/src/delegate/i18n/zh-CN.json +46 -0
  532. package/templates/native-go-vue/frontend/src/delegate/i18n/zh-TW.json +15 -5
  533. package/templates/native-go-vue/frontend/src/delegate/long-running.ts +57 -0
  534. package/templates/native-go-vue/frontend/src/delegate/save-copy.test.ts +12 -0
  535. package/templates/native-go-vue/frontend/src/delegate/save-copy.ts +24 -0
  536. package/templates/native-go-vue/frontend/src/delegate/scoped-picker.test.ts +12 -0
  537. package/templates/native-go-vue/frontend/src/delegate/scoped-picker.ts +21 -0
  538. package/templates/native-go-vue/frontend/src/files/files.test.ts +7 -0
  539. package/templates/native-go-vue/frontend/src/i18n/de.json +20 -0
  540. package/templates/native-go-vue/frontend/src/i18n/en.json +20 -0
  541. package/templates/native-go-vue/frontend/src/i18n/es.json +20 -0
  542. package/templates/native-go-vue/frontend/src/i18n/fr.json +20 -0
  543. package/templates/native-go-vue/frontend/src/i18n/it.json +20 -0
  544. package/templates/native-go-vue/frontend/src/i18n/ja.json +20 -0
  545. package/templates/native-go-vue/frontend/src/i18n/ko.json +20 -0
  546. package/templates/native-go-vue/frontend/src/i18n/zh-CN.json +20 -0
  547. package/templates/native-go-vue/frontend/src/i18n/zh-TW.json +20 -0
  548. package/templates/native-go-vue/frontend/src/i18n.ts +13 -0
  549. package/templates/native-go-vue/frontend/src/identity/LoginPage.vue +16 -14
  550. package/templates/native-go-vue/frontend/src/identity/i18n/de.json +27 -0
  551. package/templates/native-go-vue/frontend/src/identity/i18n/en.json +27 -0
  552. package/templates/native-go-vue/frontend/src/identity/i18n/es.json +27 -0
  553. package/templates/native-go-vue/frontend/src/identity/i18n/fr.json +27 -0
  554. package/templates/native-go-vue/frontend/src/identity/i18n/it.json +27 -0
  555. package/templates/native-go-vue/frontend/src/identity/i18n/ja.json +27 -0
  556. package/templates/native-go-vue/frontend/src/identity/i18n/ko.json +27 -0
  557. package/templates/native-go-vue/frontend/src/identity/i18n/zh-CN.json +27 -0
  558. package/templates/native-go-vue/frontend/src/identity/i18n/zh-TW.json +27 -0
  559. package/templates/native-go-vue/frontend/src/identity/i18n.ts +14 -0
  560. package/templates/native-go-vue/frontend/src/identity/session.test.ts +4 -0
  561. package/templates/native-go-vue/frontend/src/identity/session.ts +9 -8
  562. package/templates/native-go-vue/frontend/src/nas/NasPicker.vue +31 -12
  563. package/templates/native-go-vue/frontend/src/nas/destination.test.ts +48 -0
  564. package/templates/native-go-vue/frontend/src/nas/i18n/files/de.json +1 -1
  565. package/templates/native-go-vue/frontend/src/nas/i18n/files/en.json +1 -1
  566. package/templates/native-go-vue/frontend/src/nas/i18n/files/es.json +10 -10
  567. package/templates/native-go-vue/frontend/src/nas/i18n/files/fr.json +1 -1
  568. package/templates/native-go-vue/frontend/src/nas/i18n/files/it.json +12 -12
  569. package/templates/native-go-vue/frontend/src/nas/i18n/files/ja.json +15 -15
  570. package/templates/native-go-vue/frontend/src/nas/i18n/files/ko.json +12 -12
  571. package/templates/native-go-vue/frontend/src/nas/i18n/files/zh-CN.json +1 -1
  572. package/templates/native-go-vue/frontend/src/nas/i18n/files/zh-TW.json +1 -1
  573. package/templates/native-go-vue/frontend/src/portal/PortalSettings.vue +39 -0
  574. package/templates/native-go-vue/frontend/src/shell/i18n/fr.json +1 -1
  575. package/templates/native-go-vue/frontend/src/shell/i18n/ja.json +1 -1
  576. package/templates/native-go-vue/frontend/test/apps.test.ts +14 -0
  577. package/templates/native-go-vue/frontend/test/portal-settings.test.ts +71 -0
  578. package/templates/native-go-vue/frontend/test/shell-i18n.test.ts +50 -0
@@ -2,7 +2,8 @@ import { spawnSync } from "node:child_process";
2
2
  import { existsSync, mkdtempSync, rmSync } from "node:fs";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
- import { resolveMinDsm, DEFAULT_MIN_DSM, isDsmAtLeast } from "../build-spk/dsm-version.js";
5
+ import { goBuildEnv } from "../build-spk/dsm-arch.js";
6
+ import { resolveMinDsm, DEFAULT_MIN_DSM, isDsmAtLeast, PORTAL_MIN_DSM, portalMinDsmMessage } from "../build-spk/dsm-version.js";
6
7
  import { checkCgiBinary } from "./binary.js";
7
8
  import { loadStandaloneManifest } from "./manifest.js";
8
9
  import { assembleStandaloneSpk, StandaloneBuildError } from "./spk.js";
@@ -10,7 +11,6 @@ import { buildDaemonControl } from "./daemon.js";
10
11
  import { buildExecutor } from "../runtime/executor-build.js";
11
12
  import { BACKGROUND_WORKER_MAIN } from "./background.js";
12
13
  import { readApiPermissions } from "./api-permissions.js";
13
- const GOARCH = { x86_64: "amd64", aarch64: "arm64" };
14
14
  /** Argv only, never a shell; output streams to the terminal except stderr, which is also kept. */
15
15
  export const runCommand = (command, args, options) => {
16
16
  const result = spawnSync(command, args, {
@@ -38,11 +38,14 @@ export async function buildStandalone(options) {
38
38
  if (manifest.capabilities.rootOps === true && !isDsmAtLeast(manifest.os_min_ver, DEFAULT_MIN_DSM)) {
39
39
  throw new StandaloneBuildError("STANDALONE_OS_MIN_VER_INVALID", `root templates require DSM ${DEFAULT_MIN_DSM} or later.`);
40
40
  }
41
+ if (manifest.portal && !isDsmAtLeast(manifest.os_min_ver, PORTAL_MIN_DSM)) {
42
+ throw new StandaloneBuildError("STANDALONE_PORTAL_MIN_DSM", `[STANDALONE_PORTAL_MIN_DSM] ${portalMinDsmMessage(manifest.os_min_ver, "portal")}`);
43
+ }
41
44
  const run = options.run ?? runCommand;
42
45
  const arches = options.arches ?? manifest.arches;
43
46
  for (const arch of arches) {
44
47
  if (!manifest.arches.includes(arch)) {
45
- throw new StandaloneBuildError("STANDALONE_ARCH_NOT_DECLARED", `${arch} 不在 dsm-package.yaml 的 arches 內。`);
48
+ throw new StandaloneBuildError("STANDALONE_ARCH_NOT_DECLARED", `${arch} 不在 dsm-package.yaml 的 arches 內;要建置它,請先把 ${arch} 加進 arches。`);
46
49
  }
47
50
  }
48
51
  buildUi(manifest, run);
@@ -60,7 +63,7 @@ export async function buildStandalone(options) {
60
63
  }
61
64
  const outputPath = join(outDir, `${manifest.package}-${manifest.spkVersion}-${arch}.spk`);
62
65
  const daemonControl = buildDaemonControl(manifest, arch, work, run);
63
- const backgroundWorker = buildBackgroundWorker(manifest, arch, work, run);
66
+ const backgroundWorker = buildBackgroundWorker(builtManifest, arch, work, run);
64
67
  await assembleStandaloneSpk({ manifest: builtManifest, arch, cgiPath, outputPath, daemonControl, backgroundWorker, executor });
65
68
  outputs.push(outputPath);
66
69
  }
@@ -103,14 +106,16 @@ function backendFor(manifest, arch, work, run) {
103
106
  `-X main.version=${manifest.spkVersion}`,
104
107
  `-X dsmapp/internal/api.expectedPermissionsDigest=${manifest.apiPermissionsDigest}`,
105
108
  `-X dsmapp/internal/api.expectedSDKPermissionsDigest=${manifest.apiSDKPermissionsDigest}`,
106
- ...(manifest.capabilities.delegate ? [`-X ${JSON.stringify(`main.delegateScope=${manifest.capabilities.delegate.scope}`)}`] : []),
109
+ ...delegateBuildFlags(manifest),
107
110
  ...(manifest.executor ? [`-X main.executorDigest=${manifest.executor.executorDigest}`, `-X main.policyDigest=${manifest.executor.policyDigest}`] : []),
108
111
  ...(manifest.capabilities.identity ? [`-X main.identity=${manifest.capabilities.identity}`] : []),
109
112
  ...(manifest.capabilities.files || manifest.capabilities.delegate ? [`-X main.fileStation=session`] : []),
113
+ ...(manifest.entry ? [`-X main.entry=${manifest.entry}`] : []),
114
+ ...(manifest.portal ? ["-X main.portal=on"] : []),
110
115
  ].join(" ");
111
116
  const result = run("go", ["build", "-trimpath", "-buildvcs=false", "-ldflags", ldflags, "-o", out, backend.main], {
112
117
  cwd: backend.dir,
113
- env: { ...process.env, CGO_ENABLED: "0", GOOS: "linux", GOARCH: GOARCH[arch] },
118
+ env: { ...process.env, ...goBuildEnv(arch) },
114
119
  });
115
120
  if (result.missing) {
116
121
  throw new StandaloneBuildError("STANDALONE_GO_MISSING", "找不到 go 指令。請安裝 Go 1.25 以上版本(https://go.dev/dl/),或改用 backend.binary 打包既有執行檔。");
@@ -125,10 +130,12 @@ function buildBackgroundWorker(manifest, arch, work, run) {
125
130
  if (!manifest.capabilities.jobs || manifest.backend.kind !== "go")
126
131
  return undefined;
127
132
  const out = join(work, `background-worker-${arch}`);
128
- const ldflags = ["-s", "-w", "-buildid=", `-X main.packageID=${manifest.package}`].join(" ");
133
+ const ldflags = ["-s", "-w", "-buildid=", `-X main.packageID=${manifest.package}`, ...delegateBuildFlags(manifest),
134
+ ...(manifest.executor ? [`-X main.executorDigest=${manifest.executor.executorDigest}`, `-X main.policyDigest=${manifest.executor.policyDigest}`] : []),
135
+ ].join(" ");
129
136
  const result = run("go", ["build", "-trimpath", "-buildvcs=false", "-ldflags", ldflags, "-o", out, BACKGROUND_WORKER_MAIN], {
130
137
  cwd: manifest.backend.dir,
131
- env: { ...process.env, CGO_ENABLED: "0", GOOS: "linux", GOARCH: GOARCH[arch] },
138
+ env: { ...process.env, ...goBuildEnv(arch) },
132
139
  });
133
140
  if (result.missing || result.status !== 0) {
134
141
  throw new StandaloneBuildError("STANDALONE_BACKGROUND_BUILD_FAILED", `background worker(${BACKGROUND_WORKER_MAIN})build 失敗;這個專案的 backend 需要含該 package。\n${result.stderr}`);
@@ -138,3 +145,26 @@ function buildBackgroundWorker(manifest, arch, work, run) {
138
145
  throw new StandaloneBuildError(problem.code, problem.message);
139
146
  return out;
140
147
  }
148
+ function delegateBuildFlags(manifest) {
149
+ const d = manifest.capabilities.delegate;
150
+ if (!d)
151
+ return [];
152
+ const values = {
153
+ delegateScope: d.scope,
154
+ delegateOperationsJSON: JSON.stringify([...d.operations].sort()),
155
+ delegateDestinationsJSON: JSON.stringify(d.destinations ? [...d.destinations].sort() : []),
156
+ delegateLifetime: d.lifetime ?? '',
157
+ };
158
+ // Go's quoted.Split does not process backslash escapes. Interior JSON quotes
159
+ // in a whitespace-free argument are literal; never shell-escape this string.
160
+ return Object.entries(values).map(([key, value]) => {
161
+ const arg = `main.${key}=${value}`;
162
+ if (!/\s/.test(arg))
163
+ return `-X ${arg}`;
164
+ if (!arg.includes("'"))
165
+ return `-X '${arg}'`;
166
+ if (!arg.includes('"'))
167
+ return `-X "${arg}"`;
168
+ throw new StandaloneBuildError('STANDALONE_GO_BUILD_FAILED', 'Delegate scope with whitespace and both quote types cannot be represented in Go linker flags.');
169
+ });
170
+ }
@@ -1,4 +1,4 @@
1
- import type { NativeArchFamily } from "../build-spk/dsm-arch.js";
1
+ import { type NativeArchFamily } from "../build-spk/dsm-arch.js";
2
2
  import type { CommandRunner } from "./build.js";
3
3
  import type { StandaloneError, StandaloneManifest } from "./manifest.js";
4
4
  export interface Daemon {
@@ -1,5 +1,6 @@
1
1
  import { chmodSync, copyFileSync, lstatSync, mkdirSync, readdirSync, realpathSync, writeFileSync } from "node:fs";
2
2
  import { isAbsolute, join, relative, resolve } from "node:path";
3
+ import { goBuildEnv } from "../build-spk/dsm-arch.js";
3
4
  import { templatesRoot } from "../template-paths.js";
4
5
  import { checkCgiBinary } from "./binary.js";
5
6
  import { StandaloneBuildError } from "./spk.js";
@@ -58,7 +59,7 @@ export function buildDaemonControl(manifest, arch, work, run) {
58
59
  return undefined;
59
60
  const out = join(work, `daemon-control-${arch}`);
60
61
  const result = run("go", ["build", "-trimpath", "-buildvcs=false", "-ldflags", "-s -w -buildid=", "-o", out, "."], {
61
- cwd: join(templatesRoot(), "_daemon"), env: { ...process.env, CGO_ENABLED: "0", GOOS: "linux", GOARCH: arch === "x86_64" ? "amd64" : "arm64" },
62
+ cwd: join(templatesRoot(), "_daemon"), env: { ...process.env, ...goBuildEnv(arch) },
62
63
  });
63
64
  if (result.missing || result.status !== 0)
64
65
  throw new StandaloneBuildError("STANDALONE_DAEMON_BUILD_FAILED", `daemon controller build 需要 Go 1.24 以上。\n${result.stderr}`);
@@ -1,4 +1,4 @@
1
- import type { StandaloneManifest } from "./manifest.js";
1
+ import type { EntryMode, StandaloneManifest } from "./manifest.js";
2
2
  /** Same path the generated wrapper POSTs to, and the payload location of the CGI. */
3
3
  export declare const CGI_PAYLOAD_PATH = "ui/cgi/api.cgi";
4
4
  export declare function cgiUrl(packageId: string): string;
@@ -14,6 +14,8 @@ export declare const I18N_RUNTIME: string;
14
14
  * cached from the previous version after an upgrade.
15
15
  */
16
16
  export declare function windowScriptName(manifest: Pick<StandaloneManifest, "build">): string;
17
+ /** The launcher fields an entry mode fixes; inspect checks a built SPK against them. */
18
+ export declare function entryLauncherAccess(entry: EntryMode): Record<string, unknown>;
17
19
  /** `ui/config`: registers the DSM main-menu app and its window class. */
18
20
  export declare function renderUiConfig(manifest: StandaloneManifest): string;
19
21
  /**
@@ -3,6 +3,7 @@ import { DSM_LANGS } from "./i18n.js";
3
3
  import { ROOT_PANEL, ROOT_RUNTIME, rootTasks } from "./root-window.js";
4
4
  import { HOST_ADAPTER_RUNTIME } from "./host-adapter.js";
5
5
  import { permitsOrdinaryUsers } from "./api-permissions.js";
6
+ import { PORTAL_RUNTIME } from "./portal-window.js";
6
7
  /** Same path the generated wrapper POSTs to, and the payload location of the CGI. */
7
8
  export const CGI_PAYLOAD_PATH = "ui/cgi/api.cgi";
8
9
  export function cgiUrl(packageId) {
@@ -47,6 +48,20 @@ export const I18N_RUNTIME = `function createI18n(dsmLang, catalogs) {
47
48
  export function windowScriptName(manifest) {
48
49
  return `window-${String(manifest.build).padStart(4, "0")}.js`;
49
50
  }
51
+ /**
52
+ * Who sees the main-menu entry. app-privilege also registers the app in
53
+ * Control Panel > Application Privileges (local and directory accounts, with
54
+ * IP rules); DSM then filters the launcher and the CGI checks the same rule.
55
+ */
56
+ function launcherAccess(manifest) {
57
+ return manifest.entry ? entryLauncherAccess(manifest.entry) : { allUsers: permitsOrdinaryUsers(manifest.apiPermissions) };
58
+ }
59
+ /** The launcher fields an entry mode fixes; inspect checks a built SPK against them. */
60
+ export function entryLauncherAccess(entry) {
61
+ return entry === "admin-only"
62
+ ? { allUsers: false }
63
+ : { allUsers: true, grantPrivilege: "all", advanceGrantPrivilege: true };
64
+ }
50
65
  /** `ui/config`: registers the DSM main-menu app and its window class. */
51
66
  export function renderUiConfig(manifest) {
52
67
  const ns = `SYNO.SDS.${manifest.package}`;
@@ -59,7 +74,7 @@ export function renderUiConfig(manifest) {
59
74
  icon: "images/app_{0}.png",
60
75
  appWindow: `${ns}.MainWindow`,
61
76
  allowMultiInstance: false,
62
- allUsers: permitsOrdinaryUsers(manifest.apiPermissions),
77
+ ...launcherAccess(manifest),
63
78
  depend: [`${ns}.MainWindow`],
64
79
  },
65
80
  [`${ns}.MainWindow`]: {
@@ -127,7 +142,14 @@ ${HOST_ADAPTER_RUNTIME}
127
142
  return createHostAdapter;
128
143
  })();
129
144
 
130
- ${root ? root.definitions : ""}${ns}.loadApp = function (done) {
145
+ ${root || manifest.portal ? renderWebApi(ns) : ""}// A display hint for hiding or disabling administrator settings; the CGI
146
+ // decides access on its own and never trusts this value.
147
+ ${ns}.sessionUser = function () {
148
+ var session = (SYNO.SDS && SYNO.SDS.Session) || {};
149
+ return { name: typeof session.user === "string" ? session.user : "", isAdmin: session.is_admin === true };
150
+ };
151
+
152
+ ${root ? root.definitions : ""}${manifest.portal ? renderPortalDefinition(ns) : ""}${ns}.loadApp = function (done) {
131
153
  var apps = window.DsmPackageApps || {};
132
154
  if (apps[${p}]) {
133
155
  done(apps[${p}]);
@@ -186,6 +208,7 @@ ${root ? root.mount : ""} ${ns}.loadApp(function (app) {
186
208
  call: ${ns}.call,
187
209
  files: {endpoint: ${JSON.stringify(cgiUrl(pkg))}, token: function () { return SYNO.SDS.Session && SYNO.SDS.Session.SynoToken; }},
188
210
  reopen: me.reopenEvents.subscribe,
211
+ user: ${ns}.sessionUser(),${manifest.portal ? `\n portal: ${renderPortalAccess(ns, manifest)},` : ""}
189
212
  host: me.hostAccess,
190
213
  appWindow: me,
191
214
  lang: lang,
@@ -221,6 +244,50 @@ ${root ? root.mount : ""} ${ns}.loadApp(function (app) {
221
244
  }
222
245
  }
223
246
  });
247
+ `;
248
+ }
249
+ function renderPortalDefinition(ns) {
250
+ return `${ns}.createPortalAccess = (function () {
251
+ ${PORTAL_RUNTIME}
252
+ return createPortalAccess;
253
+ })();
254
+
255
+ `;
256
+ }
257
+ /**
258
+ * `ctx.portal`: administrators use their own DSM session; isAdmin only picks
259
+ * the path. The SDK ships the panel text in every DSM language, so it follows
260
+ * DSM even where the app has no catalog.
261
+ */
262
+ function renderPortalAccess(ns, manifest) {
263
+ return `${ns}.createPortalAccess({
264
+ appId: ${JSON.stringify(`${ns}.Instance`)},
265
+ defaultAlias: ${JSON.stringify(manifest.portal.alias)},
266
+ isAdmin: function () { return ${ns}.sessionUser().isAdmin; },
267
+ send: ${ns}.webApi(me),
268
+ call: ${ns}.call,
269
+ launch: function (app, params) { SYNO.SDS.AppLaunch(app, params); },
270
+ location: window.location,
271
+ locale: ${ns}.createI18n(lang, ${JSON.stringify(Object.fromEntries(Object.keys(DSM_LANGS).map((locale) => [locale, {}])))}).locale
272
+ })`;
273
+ }
274
+ /** DSM WebAPI in the window's own session: resolves data, rejects `{code: "dsm_<n>"}`. */
275
+ function renderWebApi(ns) {
276
+ return `// DSM WebAPI in the window's own session: resolves data, rejects {code: "dsm_<n>"}.
277
+ ${ns}.webApi = function (win) {
278
+ return function (api, method, version, params) {
279
+ return new Promise(function (resolve, reject) {
280
+ win.sendWebAPI({
281
+ api: api, method: method, version: version, params: params, scope: win,
282
+ callback: function (success, response) {
283
+ if (success) resolve(response);
284
+ else reject({ code: "dsm_" + ((response && response.code) || "error") });
285
+ }
286
+ });
287
+ });
288
+ };
289
+ };
290
+
224
291
  `;
225
292
  }
226
293
  const ROOT_PANEL_CSS = [
@@ -259,19 +326,9 @@ ${ROOT_PANEL}
259
326
  };
260
327
  var access = ${ns}.createRootAccess({
261
328
  tasks: ${ns}.rootTasks,
262
- pins: ${JSON.stringify({ executorDigest: manifest.executor.executorDigest, policyDigest: manifest.executor.policyDigest })},
329
+ pins: ${JSON.stringify({ package: manifest.package, executorDigest: manifest.executor.executorDigest, policyDigest: manifest.executor.policyDigest })},
263
330
  user: session,
264
- send: function (api, method, version, params) {
265
- return new Promise(function (resolve, reject) {
266
- me.sendWebAPI({
267
- api: api, method: method, version: version, params: params, scope: me,
268
- callback: function (success, response) {
269
- if (success) resolve(response);
270
- else reject({ code: "dsm_" + ((response && response.code) || "error") });
271
- }
272
- });
273
- });
274
- },
331
+ send: ${ns}.webApi(me),
275
332
  confirmPassword: function (suppliedPassword) {
276
333
  // Send exactly one PasswordConfirm v2 auth with a masked password field.
277
334
  return new Promise(function (resolve, reject) {
@@ -309,7 +366,7 @@ ${ROOT_PANEL}
309
366
  wait: function (ms) { return new Promise(function (resolve) { setTimeout(resolve, ms); }); }
310
367
  });
311
368
  var panels = me.body && me.body.dom.querySelector(".dsmapp-rootpanels");
312
- if (panels) {
369
+ if (panels && ${manifest.ui.build !== undefined && manifest.capabilities.delegate?.scope === "install-selected" ? "false" : "true"}) {
313
370
  ${ns}.mountRootPanel(panels, access, null, ${ns}.rootTasks, session().admin);
314
371
  }
315
372
  `,
@@ -1,4 +1,5 @@
1
1
  import { type ExecutorInspection } from "../runtime/executor-inspect.js";
2
+ import type { EntryMode } from "./manifest.js";
2
3
  import { type UninstallCleanupItem } from "../runtime/uninstall-cleanup.js";
3
4
  export type SpkKind = "native-go" | "binary" | "container" | "native-layout";
4
5
  export interface SpkInspection {
@@ -22,6 +23,13 @@ export interface SpkInspection {
22
23
  }>;
23
24
  executor?: ExecutorInspection;
24
25
  uninstallCleanup?: UninstallCleanupItem[];
26
+ /** Application Portal entry; inactive until an administrator applies the alias. */
27
+ portal?: {
28
+ appId: string;
29
+ alias: string;
30
+ };
31
+ /** Declared `entry` mode; `admin-only` is the administrator floor of every route. */
32
+ entry?: EntryMode;
25
33
  }
26
34
  /** Reads an SPK without installing it and reports how it would run on DSM. */
27
35
  export declare function inspectStandaloneSpk(spkPath: string): Promise<SpkInspection>;
@@ -1,22 +1,25 @@
1
1
  import { inspectNativeLayout } from "./native-layout.js";
2
- import { inspectExecutor } from "../runtime/executor-inspect.js";
2
+ import { inspectExecutor, inspectExecutorAnchor } from "../runtime/executor-inspect.js";
3
3
  import { existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, rmSync } from "node:fs";
4
4
  import { tmpdir } from "node:os";
5
5
  import { join } from "node:path";
6
6
  import * as tar from "tar";
7
7
  import { familyOfArchToken, NATIVE_ARCH_FAMILIES } from "../build-spk/dsm-arch.js";
8
- import { CONTAINER_MIN_DSM, isDsmAtLeast } from "../build-spk/dsm-version.js";
8
+ import { CONTAINER_MIN_DSM, isDsmAtLeast, PORTAL_MIN_DSM } from "../build-spk/dsm-version.js";
9
9
  import { ELF_MACHINE } from "./binary.js";
10
- import { CGI_PAYLOAD_PATH } from "./dsm-window.js";
10
+ import { CGI_PAYLOAD_PATH, entryLauncherAccess } from "./dsm-window.js";
11
11
  import { daemonConfig, readDaemon } from "./daemon.js";
12
12
  import { BACKGROUND_UNIT_DIR, BACKGROUND_WORKER_PAYLOAD, backgroundUnitName, renderBackgroundUnit } from "./background.js";
13
13
  import { renderStandaloneResource } from "./resource.js";
14
14
  import { recoverSharedFolder, renderSharedFolderUpgradeWizardFiles, sharedFolderInstallSteps, SHARED_FOLDER_MARKER } from "./shared-folder.js";
15
15
  import { renderInstallWizardFiles, renderNativeInstallWizard } from "../build-spk/install-wizard.js";
16
+ import { renderDelegateScopeSteps, renderDelegateInstallDraft } from "../build-spk/delegate-scope.js";
16
17
  import { renderUninstallWizardFiles } from "../build-spk/uninstall-wizard.js";
17
18
  import { renderStandaloneScript } from "./lifecycle.js";
18
19
  import { listPortExposures, loadCompose } from "../compose/analyze.js";
19
20
  import { uninstallCleanup } from "../runtime/uninstall-cleanup.js";
21
+ import { PORTAL_JSON_RELPATH, PORTAL_MUSTACHE_RELPATH, portalResource, renderPortalJson, renderPortalLifecycle, renderPortalMustache, renderPortalWizardStep } from "../portal/package.js";
22
+ import { isPortalAlias } from "../portal/alias.js";
20
23
  const ROOT_SCRIPT_HINTS = [/\bsudo\b/, /\bchown\s+(-\w+\s+)*root\b/, /chmod\s+[ugo]*\+s\b/, /chmod\s+[2467][0-7]{3}\b/];
21
24
  /** Reads an SPK without installing it and reports how it would run on DSM. */
22
25
  export async function inspectStandaloneSpk(spkPath) {
@@ -48,7 +51,7 @@ export async function inspectSpk(spkPath, expectedKind) {
48
51
  if (metadataText !== undefined) {
49
52
  try {
50
53
  const raw = JSON.parse(metadataText);
51
- if (raw.schema !== 2 || !["go", "binary"].includes(raw.backend) || !Array.isArray(raw.capabilities) || raw.capabilities.some((name) => !["files", "jobs", "identity", "rootOps", "delegate"].includes(String(name))) || new Set(raw.capabilities).size !== raw.capabilities.length)
54
+ if (raw.schema !== 2 || !["go", "binary"].includes(raw.backend) || !Array.isArray(raw.capabilities) || raw.capabilities.some((name) => !["files", "jobs", "identity", "rootOps", "delegate"].includes(String(name))) || new Set(raw.capabilities).size !== raw.capabilities.length || (raw.entry !== undefined && !["admin-only", "app-privilege"].includes(raw.entry)))
52
55
  throw new Error();
53
56
  metadata = raw;
54
57
  }
@@ -63,18 +66,19 @@ export async function inspectSpk(spkPath, expectedKind) {
63
66
  await checkArch(dir, info, kind, paths.has(CGI_PAYLOAD_PATH), findings);
64
67
  const background = kind === "native-go" && paths.has(BACKGROUND_WORKER_PAYLOAD);
65
68
  const sharedFolder = standalone ? recoverSharedFolder(readOptional(join(dir, "scripts/postinst"))) : undefined;
66
- checkConf(dir, findings, kind, background, sharedFolder, info.package);
69
+ const portal = await recoverPortal(dir, info, kind === "container" ? info.package ?? "" : `SYNO.SDS.${info.package}.Instance`, paths, payload, findings);
70
+ if (portal && info.os_min_ver && OS_MIN_VER.test(info.os_min_ver) && !isDsmAtLeast(info.os_min_ver, PORTAL_MIN_DSM)) {
71
+ findings.push(`INFO os_min_ver「${info.os_min_ver}」低於 portal 需要的 DSM ${PORTAL_MIN_DSM};DSM 7.1.1 以錯誤 320 拒裝帶 web-config app-portal 的未簽章套件,7.2 未驗證`);
72
+ }
73
+ checkConf(dir, findings, kind, background, sharedFolder, info.package, portal);
67
74
  checkScripts(dir, findings);
68
- if (standalone)
69
- checkSharedFolder(dir, findings, info.package, sharedFolder);
70
75
  checkUninstall(dir, findings);
71
76
  if (kind === "container")
72
77
  await checkContainerExposure(dir, join(dir, "package.tgz"), findings);
73
78
  const hasExecutor = (metadata.capabilities ?? []).some(name => name === "rootOps" || name === "delegate") || paths.has("executor/executor");
79
+ const daemon = standalone ? await checkDaemon(dir, info, paths, payload, findings, hasExecutor, background) : undefined;
74
80
  if (background)
75
- checkBackground(dir, info, payload, findings, hasExecutor);
76
- if (standalone)
77
- await checkDaemon(dir, info, paths, payload, findings, hasExecutor);
81
+ checkBackground(dir, info, payload, findings, hasExecutor, daemon);
78
82
  let executor;
79
83
  if ((metadata.capabilities ?? []).some(name => name === "rootOps" || name === "delegate") || paths.has("executor/executor")) {
80
84
  const executorDir = join(dir, "executor-inspection");
@@ -83,6 +87,8 @@ export async function inspectSpk(spkPath, expectedKind) {
83
87
  const inspected = inspectExecutor(executorDir, info.package, info.arch);
84
88
  findings.push(...inspected.findings);
85
89
  executor = inspected.executor;
90
+ if (executor)
91
+ findings.push(...inspectExecutorAnchor(readFileSync(join(dir, "INFO"), "utf8"), executor.executorDigest));
86
92
  if (payload.find(p => p.path === "executor/executor")?.mode !== "0755")
87
93
  findings.push("Executor must be executable.");
88
94
  if (payload.find(p => p.path === "executor/lifecycle")?.mode !== "0755")
@@ -99,6 +105,13 @@ export async function inspectSpk(spkPath, expectedKind) {
99
105
  findings.push(`Executor WIZARD_UIFILES/${name} is missing cleanup guidance.`);
100
106
  }
101
107
  }
108
+ if (background && daemon && (executor?.policy.lifetime !== 'until-revoked' || !metadata.capabilities?.includes('delegate'))) {
109
+ findings.push('daemon/background combination requires an explicitly pinned until-revoked delegate policy.');
110
+ }
111
+ if (metadata.entry)
112
+ await checkEntryLauncher(dir, info.package, metadata.entry, findings);
113
+ if (standalone)
114
+ checkSharedFolder(dir, findings, info.package, sharedFolder, executor?.policy.scope === 'install-selected', portal);
102
115
  const cgiEntry = payload.find((entry) => entry.path === CGI_PAYLOAD_PATH);
103
116
  if (standalone && !cgiEntry) {
104
117
  findings.push(`payload 沒有 ${CGI_PAYLOAD_PATH}`);
@@ -109,6 +122,8 @@ export async function inspectSpk(spkPath, expectedKind) {
109
122
  return {
110
123
  kind,
111
124
  ...(executor ? { executor } : {}),
125
+ ...(portal ? { portal: { appId: portal.appId, alias: portal.alias } } : {}),
126
+ ...(metadata.entry ? { entry: metadata.entry } : {}),
112
127
  ...(standalone ? { uninstallCleanup: uninstallCleanup(info.package, hasExecutor) } : {}),
113
128
  package: info.package,
114
129
  version: info.version,
@@ -124,6 +139,23 @@ export async function inspectSpk(spkPath, expectedKind) {
124
139
  rmSync(dir, { recursive: true, force: true });
125
140
  }
126
141
  }
142
+ /** The launcher in ui/config must carry exactly the access the declared entry mode fixes. */
143
+ async function checkEntryLauncher(dir, pkg, entry, findings) {
144
+ const target = join(dir, "entry-inspection");
145
+ mkdirSync(target);
146
+ await tar.x({ file: join(dir, "package.tgz"), cwd: target, filter: path => path.replace(/^\.\//, "") === "ui/config" });
147
+ let launcher;
148
+ try {
149
+ const config = JSON.parse(readFileSync(join(target, "ui/config"), "utf8"));
150
+ launcher = Object.values(config)[0]?.[`SYNO.SDS.${pkg}.Instance`];
151
+ }
152
+ catch { /* reported below */ }
153
+ const expected = entryLauncherAccess(entry);
154
+ const fields = ["allUsers", "grantPrivilege", "advanceGrantPrivilege"];
155
+ if (!launcher || fields.some(field => launcher[field] !== expected[field])) {
156
+ findings.push(`ENTRY_LAUNCHER_MISMATCH sdk-package.json 宣告 entry: ${entry},但 ui/config 的 launcher 不是 ${JSON.stringify(expected)};請用 synopkgland build 重新產生`);
157
+ }
158
+ }
127
159
  const OS_MIN_VER = /^7\.\d+(\.\d+)?-\d+$/;
128
160
  /** INFO os_min_ver and arch must be values DSM accepts, and arch must match the shipped CGI. */
129
161
  async function checkArch(dir, info, kind, hasCgi, findings) {
@@ -157,7 +189,7 @@ async function checkArch(dir, info, kind, hasCgi, findings) {
157
189
  findings.push(`INFO arch 宣告 ${declared},但 ${CGI_PAYLOAD_PATH} 是 ${actual} 的執行檔`);
158
190
  }
159
191
  }
160
- async function checkDaemon(dir, info, paths, payload, findings, executor) {
192
+ async function checkDaemon(dir, info, paths, payload, findings, executor, background) {
161
193
  if (![...paths].some(path => path.startsWith("daemon/")))
162
194
  return;
163
195
  if (info.precheckstartstop !== "yes")
@@ -178,13 +210,14 @@ async function checkDaemon(dir, info, paths, payload, findings, executor) {
178
210
  throw new Error("invalid config");
179
211
  if (payload.find(p => p.path === `daemon/payload/${daemon.exec}`)?.mode !== "0755")
180
212
  throw new Error("missing executable");
181
- const manifest = { package: info.package, daemon, capabilities: executor ? { rootOps: true } : {} };
213
+ const manifest = { package: info.package, daemon, capabilities: { ...(executor ? { rootOps: true } : {}), ...(background ? { jobs: { worker: 'sha256-v1' } } : {}) } };
182
214
  if (readFileSync(join(unpacked, "daemon/config.json"), "utf8") !== daemonConfig(manifest))
183
215
  throw new Error("non-canonical daemon config");
184
216
  for (const hook of ["start-stop-status", "preupgrade", "preuninst"]) {
185
217
  if (readFileSync(join(dir, "scripts", hook), "utf8") !== renderStandaloneScript(hook, manifest))
186
218
  findings.push(`daemon scripts/${hook} 與固定 lifecycle 不符`);
187
219
  }
220
+ return daemon;
188
221
  }
189
222
  catch {
190
223
  findings.push("daemon/config.json 或 payload 不符合固定 daemon 契約");
@@ -234,12 +267,12 @@ async function checkContainerExposure(dir, packageTgz, findings) {
234
267
  function readOptional(path) {
235
268
  return existsSync(path) ? readFileSync(path, "utf8") : undefined;
236
269
  }
237
- function checkConf(dir, findings, kind, background, sharedFolder, pkg) {
270
+ function checkConf(dir, findings, kind, background, sharedFolder, pkg, portal) {
238
271
  const confDir = join(dir, "conf");
239
272
  const entries = existsSync(confDir) ? readdirSync(confDir) : [];
240
- const nativeResource = kind !== "container" ? renderStandaloneResource({ package: pkg ?? "", capabilities: background ? { jobs: { worker: "sha256-v1" } } : {}, ...(sharedFolder ? { sharedFolder } : {}) }) : undefined;
273
+ const nativeResource = kind !== "container" ? renderStandaloneResource({ package: pkg ?? "", capabilities: background ? { jobs: { worker: "sha256-v1" } } : {}, ...(sharedFolder ? { sharedFolder } : {}) }, portal) : undefined;
241
274
  if (nativeResource !== undefined && readOptional(join(confDir, "resource")) !== nativeResource) {
242
- findings.push(`conf/resource 必須是 ${nativeResource}(只有官方 data-share 與 systemd-user-unit,且與 install.sharedFolder 宣告一致)`);
275
+ findings.push(`conf/resource 必須是 ${nativeResource}(只有官方 data-share、systemd-user-unit 與 web-config,且與 install.sharedFolder、portal 宣告一致)`);
243
276
  }
244
277
  for (const name of entries) {
245
278
  if (name !== "privilege" && !(kind === "container" && name === "resource") && !(nativeResource !== undefined && name === "resource") && !(background && name === "systemd")) {
@@ -252,9 +285,11 @@ function checkConf(dir, findings, kind, background, sharedFolder, pkg) {
252
285
  if (!resource["docker-project"])
253
286
  findings.push("container 缺少 docker-project resource");
254
287
  for (const key of Object.keys(resource)) {
255
- if (!["docker-project", "webservice"].includes(key))
288
+ if (!["docker-project", "webservice", ...(portal ? ["web-config"] : [])].includes(key))
256
289
  findings.push(`未知 container resource: ${key}`);
257
290
  }
291
+ if (portal && JSON.stringify(resource["web-config"]) !== JSON.stringify(portalResource()["web-config"]))
292
+ findings.push("container web-config 必須只有固定的 app-portal 註冊");
258
293
  }
259
294
  catch {
260
295
  findings.push("container 缺少有效的 conf/resource");
@@ -283,19 +318,21 @@ function checkConf(dir, findings, kind, background, sharedFolder, pkg) {
283
318
  }
284
319
  }
285
320
  /** The opt-in package user unit must be exactly what the generator writes. */
286
- function checkBackground(dir, info, payload, findings, executor) {
321
+ function checkBackground(dir, info, payload, findings, executor, daemon) {
287
322
  const pkg = info.package;
288
323
  if (!pkg)
289
324
  return;
290
325
  const read = (path) => existsSync(join(dir, path)) ? readFileSync(join(dir, path), "utf8") : undefined;
291
326
  if (payload.find(p => p.path === BACKGROUND_WORKER_PAYLOAD)?.mode !== "0755")
292
327
  findings.push(`${BACKGROUND_WORKER_PAYLOAD} 缺少或非 executable`);
328
+ if (info.precheckstartstop !== "yes")
329
+ findings.push('background INFO 必須明確設定 precheckstartstop="yes"(prestart 復原失去 socket 的套件使用者 manager)');
293
330
  const units = existsSync(join(dir, BACKGROUND_UNIT_DIR)) ? readdirSync(join(dir, BACKGROUND_UNIT_DIR)) : [];
294
331
  const unit = backgroundUnitName(pkg);
295
332
  if (units.length !== 1 || units[0] !== unit || read(`${BACKGROUND_UNIT_DIR}/${unit}`) !== renderBackgroundUnit(pkg)) {
296
333
  findings.push(`${BACKGROUND_UNIT_DIR} 必須只有固定的 ${unit}(套件帳號執行,無 User=、無 root)`);
297
334
  }
298
- if (read("scripts/start-stop-status") !== renderStandaloneScript("start-stop-status", { package: pkg, capabilities: { jobs: { worker: "sha256-v1" }, ...(executor ? { rootOps: true } : {}) } }))
335
+ if (read("scripts/start-stop-status") !== renderStandaloneScript("start-stop-status", { package: pkg, ...(daemon ? { daemon } : {}), capabilities: { jobs: { worker: "sha256-v1" }, ...(executor ? { rootOps: true } : {}) } }))
299
336
  findings.push("background scripts/start-stop-status 與固定 synosystemctl lifecycle 不符");
300
337
  }
301
338
  /**
@@ -304,7 +341,7 @@ function checkBackground(dir, info, payload, findings, executor) {
304
341
  * declaration (without one, any present file must be the default native
305
342
  * install wizard), and no other install/upgrade wizard file may exist.
306
343
  */
307
- function checkSharedFolder(dir, findings, pkg, folder) {
344
+ function checkSharedFolder(dir, findings, pkg, folder, selected = false, portal) {
308
345
  const wizardDir = join(dir, "WIZARD_UIFILES");
309
346
  const present = (existsSync(wizardDir) ? readdirSync(wizardDir) : []).filter(name => /^(install|upgrade)_uifile(_|\.|$)/.test(name)).sort();
310
347
  const postinst = readOptional(join(dir, "scripts/postinst"));
@@ -312,27 +349,69 @@ function checkSharedFolder(dir, findings, pkg, folder) {
312
349
  findings.push("scripts/postinst 的 install.sharedFolder 宣告無效");
313
350
  if (!pkg)
314
351
  return;
315
- const wizards = expectedWizardFiles(pkg, folder);
316
- const source = folder ? "install.sharedFolder 宣告" : "native-go 預設安裝精靈";
317
- // The default native wizard is optional; a declared shared folder needs all of its files.
318
- for (const name of folder ? Object.keys(wizards) : present.filter(n => n in wizards)) {
352
+ const wizards = expectedWizardFiles(pkg, folder, selected, portal);
353
+ const source = selected ? "install-selected policy" : folder ? "install.sharedFolder 宣告" : portal ? "portal 宣告" : "native-go 預設安裝精靈";
354
+ // The default native wizard is optional; a declared shared folder or portal needs all of its files.
355
+ for (const name of folder || selected || portal ? Object.keys(wizards) : present.filter(n => n in wizards)) {
319
356
  if (readOptional(join(wizardDir, name)) !== wizards[name])
320
357
  findings.push(`WIZARD_UIFILES/${name} 與${source}不符`);
321
358
  }
322
359
  for (const name of present.filter(n => !(n in wizards)))
323
360
  findings.push(`WIZARD_UIFILES/${name} 不是${source}產生的精靈檔`);
361
+ if (selected) {
362
+ for (const hook of ['preinst', 'postinst']) {
363
+ if (!readOptional(join(dir, 'scripts', hook))?.includes(renderDelegateInstallDraft(pkg, hook === 'preinst')))
364
+ findings.push(`scripts/${hook} missing install-selected draft handling`);
365
+ }
366
+ }
324
367
  if (!folder)
325
368
  return;
326
369
  for (const hook of ["postinst", "postupgrade"]) {
327
- if (readOptional(join(dir, "scripts", hook)) !== renderStandaloneScript(hook, { package: pkg, sharedFolder: folder })) {
370
+ if (readOptional(join(dir, "scripts", hook)) !== renderStandaloneScript(hook, { package: pkg, sharedFolder: folder, ...(selected ? { capabilities: { delegate: { scope: "install-selected" } } } : {}), ...portalManifest(portal) })) {
328
371
  findings.push(`scripts/${hook} 與 install.sharedFolder 宣告的固定腳本不符`);
329
372
  }
330
373
  }
331
374
  }
332
- function expectedWizardFiles(pkg, folder) {
333
- const install = renderInstallWizardFiles(pkg, folder ? sharedFolderInstallSteps(pkg, folder) : renderNativeInstallWizard);
375
+ function expectedWizardFiles(pkg, folder, selected = false, portal) {
376
+ const steps = selected ? (t) => [...renderDelegateScopeSteps(t), ...(folder ? sharedFolderInstallSteps(pkg, folder)(t) : [])] : folder ? sharedFolderInstallSteps(pkg, folder) : renderNativeInstallWizard;
377
+ const install = renderInstallWizardFiles(pkg, portal ? (t) => [...steps(t), renderPortalWizardStep(t, portal.alias)] : steps);
334
378
  return { ...install.static, ...install.dynamic, ...(folder ? renderSharedFolderUpgradeWizardFiles(pkg, folder) : {}) };
335
379
  }
380
+ function portalManifest(portal) {
381
+ return portal ? { displayname: portal.application, portal: { alias: portal.alias } } : {};
382
+ }
383
+ /**
384
+ * The portal declaration is recovered from the payload's etc/portal.json. Its
385
+ * files and alias lifecycle must be exactly what the generator writes for the
386
+ * package's own DSM window (native AppInstance or Compose `.url` entry); activation is never part of the package.
387
+ */
388
+ async function recoverPortal(dir, info, appId, paths, payload, findings) {
389
+ const files = [PORTAL_JSON_RELPATH, PORTAL_MUSTACHE_RELPATH];
390
+ if (!files.some(path => paths.has(path)))
391
+ return;
392
+ const unpacked = join(dir, "portal-inspection");
393
+ mkdirSync(unpacked);
394
+ await tar.x({ file: join(dir, "package.tgz"), cwd: unpacked, filter: path => files.includes(path.replace(/^\.\//, "")) });
395
+ try {
396
+ if (files.some(path => payload.find(p => p.path === path)?.mode !== "0644"))
397
+ throw new Error("mode");
398
+ const json = readFileSync(join(unpacked, PORTAL_JSON_RELPATH), "utf8");
399
+ const alias = JSON.parse(json).alias;
400
+ if (!isPortalAlias(alias))
401
+ throw new Error("alias");
402
+ const portal = { appId, application: info.displayname ?? "", alias };
403
+ if (json !== renderPortalJson(portal) || readFileSync(join(unpacked, PORTAL_MUSTACHE_RELPATH), "utf8") !== renderPortalMustache())
404
+ throw new Error("content");
405
+ for (const hook of ["preinst", "postinst", "postupgrade"]) {
406
+ if (!readOptional(join(dir, "scripts", hook))?.startsWith(`#!/bin/sh\n${renderPortalLifecycle(hook, portal)}`))
407
+ throw new Error(hook);
408
+ }
409
+ return portal;
410
+ }
411
+ catch {
412
+ findings.push("PORTAL_INVALID etc/portal.json、etc/portal.mustache 或 lifecycle 不符合固定 Application Portal 契約(應開啟套件自己的 DSM 視窗)");
413
+ }
414
+ }
336
415
  function checkScripts(dir, findings) {
337
416
  const scriptsDir = join(dir, "scripts");
338
417
  if (!existsSync(join(scriptsDir, "start-stop-status"))) {