@synopackageland/cli 0.4.1 → 0.5.3

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 (699) hide show
  1. package/README.md +7 -3
  2. package/dist/authorization-sdk.d.ts +5 -0
  3. package/dist/authorization-sdk.js +5 -0
  4. package/dist/build-spk/delete-app-data.d.ts +27 -7
  5. package/dist/build-spk/delete-app-data.js +35 -34
  6. package/dist/build-spk/dsm-arch.d.ts +6 -1
  7. package/dist/build-spk/dsm-arch.js +25 -1
  8. package/dist/build-spk/dsm-version.d.ts +14 -0
  9. package/dist/build-spk/dsm-version.js +47 -0
  10. package/dist/build-spk/gateway.d.ts +31 -0
  11. package/dist/build-spk/gateway.js +55 -0
  12. package/dist/build-spk/generator.d.ts +5 -0
  13. package/dist/build-spk/generator.js +103 -107
  14. package/dist/build-spk/install-wizard.d.ts +20 -0
  15. package/dist/build-spk/install-wizard.js +76 -0
  16. package/dist/build-spk/native-lifecycle.d.ts +5 -0
  17. package/dist/build-spk/native-lifecycle.js +12 -2
  18. package/dist/build-spk/resource-worker.d.ts +9 -4
  19. package/dist/build-spk/resource-worker.js +15 -6
  20. package/dist/build-spk/share-combobox.d.ts +3 -0
  21. package/dist/build-spk/share-combobox.js +27 -0
  22. package/dist/build-spk/uninstall-wizard.d.ts +14 -0
  23. package/dist/build-spk/uninstall-wizard.js +33 -0
  24. package/dist/build-spk/wizard-i18n.d.ts +18 -0
  25. package/dist/build-spk/wizard-i18n.js +38 -0
  26. package/dist/build-spk/wizard-messages.d.ts +388 -0
  27. package/dist/build-spk/wizard-messages.js +387 -0
  28. package/dist/cli.js +132 -32
  29. package/dist/compose/analyze.d.ts +20 -0
  30. package/dist/compose/analyze.js +75 -11
  31. package/dist/contract/validate.js +33 -1
  32. package/dist/contract/web-launch.d.ts +22 -0
  33. package/dist/contract/web-launch.js +37 -0
  34. package/dist/deploy/deploy.d.ts +3 -0
  35. package/dist/deploy/deploy.js +11 -2
  36. package/dist/error-registry.js +62 -3
  37. package/dist/host/dsm-port-profile.d.ts +1 -1
  38. package/dist/host/dsm-port-profile.js +1 -1
  39. package/dist/host/open-url.d.ts +2 -0
  40. package/dist/host/open-url.js +4 -1
  41. package/dist/images/pin.d.ts +52 -0
  42. package/dist/images/pin.js +249 -0
  43. package/dist/init.d.ts +27 -3
  44. package/dist/init.js +126 -16
  45. package/dist/inspect.d.ts +3 -0
  46. package/dist/inspect.js +15 -3
  47. package/dist/standalone/authorization-window.d.ts +2 -0
  48. package/dist/standalone/authorization-window.js +28 -0
  49. package/dist/standalone/authorization.d.ts +48 -0
  50. package/dist/standalone/authorization.js +116 -0
  51. package/dist/standalone/background.d.ts +21 -0
  52. package/dist/standalone/background.js +52 -0
  53. package/dist/standalone/binary.d.ts +1 -0
  54. package/dist/standalone/binary.js +1 -1
  55. package/dist/standalone/build.d.ts +2 -0
  56. package/dist/standalone/build.js +54 -10
  57. package/dist/standalone/dsm-window.js +10 -3
  58. package/dist/standalone/helper-inventory.d.ts +15 -0
  59. package/dist/standalone/helper-inventory.js +9 -0
  60. package/dist/standalone/helper.d.ts +1 -0
  61. package/dist/standalone/helper.js +29 -11
  62. package/dist/standalone/host-adapter.d.ts +1 -1
  63. package/dist/standalone/host-adapter.js +33 -15
  64. package/dist/standalone/host-file-navigation.d.ts +7 -0
  65. package/dist/standalone/host-file-navigation.js +172 -0
  66. package/dist/standalone/inspect-helper.js +21 -6
  67. package/dist/standalone/inspect-spk.d.ts +1 -1
  68. package/dist/standalone/inspect-spk.js +211 -7
  69. package/dist/standalone/job-diagnostics.d.ts +1 -0
  70. package/dist/standalone/job-diagnostics.js +49 -0
  71. package/dist/standalone/lifecycle-helper.d.ts +1 -1
  72. package/dist/standalone/lifecycle-helper.js +2 -2
  73. package/dist/standalone/lifecycle.d.ts +1 -1
  74. package/dist/standalone/lifecycle.js +14 -4
  75. package/dist/standalone/manifest.d.ts +15 -2
  76. package/dist/standalone/manifest.js +37 -6
  77. package/dist/standalone/native-layout.d.ts +74 -0
  78. package/dist/standalone/native-layout.js +318 -0
  79. package/dist/standalone/resource.d.ts +11 -0
  80. package/dist/standalone/resource.js +13 -0
  81. package/dist/standalone/root-ops.d.ts +2 -0
  82. package/dist/standalone/root-ops.js +10 -0
  83. package/dist/standalone/root-window.d.ts +3 -1
  84. package/dist/standalone/root-window.js +156 -38
  85. package/dist/standalone/shared-folder.d.ts +39 -0
  86. package/dist/standalone/shared-folder.js +141 -0
  87. package/dist/standalone/spk.d.ts +4 -0
  88. package/dist/standalone/spk.js +47 -3
  89. package/dist/standalone/worker-contract-json.d.ts +8 -0
  90. package/dist/standalone/worker-contract-json.js +141 -0
  91. package/dist/standalone/worker-contract.d.ts +189 -0
  92. package/dist/standalone/worker-contract.js +175 -0
  93. package/dist/standalone/worker-registry.d.ts +40 -0
  94. package/dist/standalone/worker-registry.js +167 -0
  95. package/dist/templates/_authorization/main.go +104 -0
  96. package/dist/templates/_authorization/main_test.go +35 -0
  97. package/dist/templates/_daemon/go.mod +2 -0
  98. package/dist/templates/_gateway/auth.go +101 -0
  99. package/dist/templates/_gateway/auth_test.go +216 -0
  100. package/dist/templates/_gateway/cgi.go +133 -0
  101. package/dist/templates/_gateway/cgi_test.go +156 -0
  102. package/dist/templates/_gateway/csrf.go +73 -0
  103. package/dist/templates/_gateway/csrf_test.go +155 -0
  104. package/dist/templates/_gateway/directory.go +108 -0
  105. package/dist/templates/_gateway/directory_test.go +202 -0
  106. package/dist/templates/_gateway/gateway.go +412 -0
  107. package/dist/templates/_gateway/gateway_test.go +676 -0
  108. package/dist/templates/_gateway/go.mod +3 -0
  109. package/dist/templates/_gateway/groups.go +66 -0
  110. package/dist/templates/_gateway/main.go +45 -0
  111. package/dist/templates/binary/synology-app.yaml +1 -1
  112. package/dist/templates/compose-import/compose.yaml +1 -1
  113. package/dist/templates/compose-import/synology-app.yaml +1 -1
  114. package/dist/templates/hello-files/compose.yaml +1 -1
  115. package/dist/templates/hello-files/synology-app.yaml +1 -1
  116. package/dist/templates/native-go/README.md +118 -5
  117. package/dist/templates/native-go/backend/THIRD_PARTY_NOTICES.txt +3 -1
  118. package/dist/templates/native-go/backend/cmd/app/main.go +25 -6
  119. package/dist/templates/native-go/backend/cmd/app/sdk_main.go +62 -0
  120. package/dist/templates/native-go/backend/cmd/background-worker/main.go +38 -0
  121. package/dist/templates/native-go/backend/cmd/helper/main.go +27 -1
  122. package/dist/templates/native-go/backend/cmd/helper/sdk_registry.go +19 -0
  123. package/dist/templates/native-go/backend/go.mod +5 -3
  124. package/dist/templates/native-go/backend/go.sum +4 -4
  125. package/dist/templates/native-go/backend/internal/api/registry.go +17 -6
  126. package/dist/templates/native-go/backend/internal/api/server.go +7 -1
  127. package/dist/templates/native-go/backend/internal/app/authorization_none.go +5 -0
  128. package/dist/templates/native-go/backend/internal/app/background.go +270 -0
  129. package/dist/templates/native-go/backend/internal/app/background_none.go +8 -0
  130. package/dist/templates/native-go/backend/internal/app/background_owner.go +59 -0
  131. package/dist/templates/native-go/backend/internal/app/background_owner_test.go +145 -0
  132. package/dist/templates/native-go/backend/internal/app/background_schedule.go +74 -0
  133. package/dist/templates/native-go/backend/internal/app/background_schedule_test.go +62 -0
  134. package/dist/templates/native-go/backend/internal/app/background_test.go +346 -0
  135. package/dist/templates/native-go/backend/internal/app/integration_state.go +16 -0
  136. package/dist/templates/native-go/backend/internal/app/migrations.go +41 -0
  137. package/dist/templates/native-go/backend/internal/app/migrations_test.go +41 -0
  138. package/dist/templates/native-go/backend/internal/app/nas_cgi_artifact_test.go +8 -0
  139. package/dist/templates/native-go/backend/internal/app/routes.go +22 -5
  140. package/dist/templates/native-go/backend/internal/app/routes_test.go +20 -3
  141. package/dist/templates/native-go/backend/internal/app/sdk_jobs.go +70 -0
  142. package/dist/templates/native-go/backend/internal/app/sdk_jobs_test.go +116 -0
  143. package/dist/templates/native-go/backend/internal/app/sdk_routes.go +177 -0
  144. package/dist/templates/native-go/backend/internal/app/session_files.go +15 -0
  145. package/dist/templates/native-go/backend/internal/app/session_files_test.go +26 -0
  146. package/dist/templates/native-go/backend/internal/app/shared_folder.go +144 -0
  147. package/dist/templates/native-go/backend/internal/app/shared_folder_test.go +158 -0
  148. package/dist/templates/native-go/backend/internal/background/access.go +57 -0
  149. package/dist/templates/native-go/backend/internal/background/owner_test.go +126 -0
  150. package/dist/templates/native-go/backend/internal/background/runner.go +185 -0
  151. package/dist/templates/native-go/backend/internal/background/runner_test.go +209 -0
  152. package/dist/templates/native-go/backend/internal/background/schedule.go +300 -0
  153. package/dist/templates/native-go/backend/internal/background/schedule_test.go +335 -0
  154. package/dist/templates/native-go/backend/internal/background/store.go +807 -0
  155. package/dist/templates/native-go/backend/internal/background/store_test.go +407 -0
  156. package/dist/templates/native-go/backend/internal/datamigrate/migrate.go +191 -0
  157. package/dist/templates/native-go/backend/internal/datamigrate/migrate_test.go +177 -0
  158. package/dist/templates/native-go/backend/internal/filestation/browse_test.go +67 -0
  159. package/dist/templates/native-go/backend/internal/filestation/client.go +251 -0
  160. package/dist/templates/native-go/backend/internal/filestation/client_test.go +155 -0
  161. package/dist/templates/native-go/backend/internal/filestation/doc.go +4 -0
  162. package/dist/templates/native-go/backend/internal/filestation/read_file_test.go +90 -0
  163. package/dist/templates/native-go/backend/internal/filestation/repository.go +207 -0
  164. package/dist/templates/native-go/backend/internal/filestation/repository_test.go +104 -0
  165. package/dist/templates/native-go/backend/internal/filestation/sdk_session.go +241 -0
  166. package/dist/templates/native-go/backend/internal/filestation/sdk_session_test.go +183 -0
  167. package/dist/templates/native-go/backend/internal/filestation/service.go +254 -0
  168. package/dist/templates/native-go/backend/internal/filestation/service_test.go +94 -0
  169. package/dist/templates/native-go/backend/internal/filestation/session_semantics_test.go +383 -0
  170. package/dist/templates/native-go/backend/internal/filestation/write.go +72 -0
  171. package/dist/templates/native-go/backend/internal/filestation/write_test.go +123 -0
  172. package/dist/templates/native-go/backend/internal/helper/background_proof.go +163 -0
  173. package/dist/templates/native-go/backend/internal/helper/background_proof_contract.go +46 -0
  174. package/dist/templates/native-go/backend/internal/helper/background_proof_test.go +161 -0
  175. package/dist/templates/native-go/backend/internal/helper/data_contract.go +3 -0
  176. package/dist/templates/native-go/backend/internal/helper/data_protocol.go +3 -0
  177. package/dist/templates/native-go/backend/internal/helper/data_protocol_test.go +1 -1
  178. package/dist/templates/native-go/backend/internal/helper/grant_authority.go +3 -0
  179. package/dist/templates/native-go/backend/internal/helper/grant_authority_test.go +1 -1
  180. package/dist/templates/native-go/backend/internal/helper/grant_repository_linux.go +2 -1
  181. package/dist/templates/native-go/backend/internal/helper/grant_repository_linux_test.go +4 -3
  182. package/dist/templates/native-go/backend/internal/helper/grant_snapshot.go +3 -0
  183. package/dist/templates/native-go/backend/internal/helper/jobcontract/bindings.go +48 -0
  184. package/dist/templates/native-go/backend/internal/helper/jobcontract/contract_test.go +436 -0
  185. package/dist/templates/native-go/backend/internal/helper/jobcontract/decode.go +185 -0
  186. package/dist/templates/native-go/backend/internal/helper/jobcontract/digest.go +55 -0
  187. package/dist/templates/native-go/backend/internal/helper/jobcontract/registry.go +85 -0
  188. package/dist/templates/native-go/backend/internal/helper/jobcontract/registry_test.go +73 -0
  189. package/dist/templates/native-go/backend/internal/helper/jobcontract/result.go +53 -0
  190. package/dist/templates/native-go/backend/internal/helper/jobcontract/stub.go +8 -0
  191. package/dist/templates/native-go/backend/internal/helper/jobcontract/testdata/input-digests.json +127 -0
  192. package/dist/templates/native-go/backend/internal/helper/jobcontract/types.go +234 -0
  193. package/dist/templates/native-go/backend/internal/helper/jobcontract/types_test.go +29 -0
  194. package/dist/templates/native-go/backend/internal/helper/jobcontract/validate.go +462 -0
  195. package/dist/templates/native-go/backend/internal/helper/legacy_authority_schema_linux_test.go +34 -0
  196. package/dist/templates/native-go/backend/internal/helper/legacy_authority_schema_test.go +6 -0
  197. package/dist/templates/native-go/backend/internal/helper/management.go +86 -0
  198. package/dist/templates/native-go/backend/internal/helper/observation.go +42 -2
  199. package/dist/templates/native-go/backend/internal/helper/observation_health_test.go +13 -1
  200. package/dist/templates/native-go/backend/internal/helper/observer_diagnostic.go +56 -10
  201. package/dist/templates/native-go/backend/internal/helper/observer_diagnostic_linux_test.go +6 -0
  202. package/dist/templates/native-go/backend/internal/helper/observer_diagnostic_writer.go +40 -0
  203. package/dist/templates/native-go/backend/internal/helper/process_linux.go +3 -0
  204. package/dist/templates/native-go/backend/internal/helper/purge.go +115 -0
  205. package/dist/templates/native-go/backend/internal/helper/purge_test.go +59 -0
  206. package/dist/templates/native-go/backend/internal/helper/query_deadline_linux_test.go +115 -0
  207. package/dist/templates/native-go/backend/internal/helper/query_pending_diagnostic_test.go +254 -0
  208. package/dist/templates/native-go/backend/internal/helper/relaxed_invalidation.go +3 -0
  209. package/dist/templates/native-go/backend/internal/helper/revoke.go +109 -0
  210. package/dist/templates/native-go/backend/internal/helper/revoke_test.go +121 -0
  211. package/dist/templates/native-go/backend/internal/helper/s1r_actor_races_linux_test.go +23 -4
  212. package/dist/templates/native-go/backend/internal/helper/sdk_assembly_linux.go +51 -0
  213. package/dist/templates/native-go/backend/internal/helper/sdk_attach.go +83 -0
  214. package/dist/templates/native-go/backend/internal/helper/sdk_attach_contract.go +32 -0
  215. package/dist/templates/native-go/backend/internal/helper/sdk_attach_test.go +184 -0
  216. package/dist/templates/native-go/backend/internal/helper/sdk_authority_schema_test.go +6 -0
  217. package/dist/templates/native-go/backend/internal/helper/sdk_background_authority.go +832 -0
  218. package/dist/templates/native-go/backend/internal/helper/sdk_background_authority_test.go +714 -0
  219. package/dist/templates/native-go/backend/internal/helper/sdk_background_invalidation.go +218 -0
  220. package/dist/templates/native-go/backend/internal/helper/sdk_background_lease.go +218 -0
  221. package/dist/templates/native-go/backend/internal/helper/sdk_background_snapshot.go +110 -0
  222. package/dist/templates/native-go/backend/internal/helper/sdk_background_store_linux_test.go +261 -0
  223. package/dist/templates/native-go/backend/internal/helper/sdk_background_store_test.go +58 -0
  224. package/dist/templates/native-go/backend/internal/helper/sdk_bindings.go +60 -0
  225. package/dist/templates/native-go/backend/internal/helper/sdk_data_contract.go +251 -0
  226. package/dist/templates/native-go/backend/internal/helper/sdk_data_protocol.go +424 -0
  227. package/dist/templates/native-go/backend/internal/helper/sdk_dispatch_absence_test.go +51 -0
  228. package/dist/templates/native-go/backend/internal/helper/sdk_display.go +488 -0
  229. package/dist/templates/native-go/backend/internal/helper/sdk_display_contract.go +121 -0
  230. package/dist/templates/native-go/backend/internal/helper/sdk_display_test.go +459 -0
  231. package/dist/templates/native-go/backend/internal/helper/sdk_grant_authority.go +559 -0
  232. package/dist/templates/native-go/backend/internal/helper/sdk_grant_repository_linux.go +417 -0
  233. package/dist/templates/native-go/backend/internal/helper/sdk_grant_snapshot.go +94 -0
  234. package/dist/templates/native-go/backend/internal/helper/sdk_history.go +80 -0
  235. package/dist/templates/native-go/backend/internal/helper/sdk_history_test.go +76 -0
  236. package/dist/templates/native-go/backend/internal/helper/sdk_job.go +697 -0
  237. package/dist/templates/native-go/backend/internal/helper/sdk_job_child_linux.go +309 -0
  238. package/dist/templates/native-go/backend/internal/helper/sdk_job_contract.go +122 -0
  239. package/dist/templates/native-go/backend/internal/helper/sdk_job_linux_test.go +159 -0
  240. package/dist/templates/native-go/backend/internal/helper/sdk_job_process_linux.go +327 -0
  241. package/dist/templates/native-go/backend/internal/helper/sdk_job_test.go +501 -0
  242. package/dist/templates/native-go/backend/internal/helper/sdk_job_vm_linux_test.go +343 -0
  243. package/dist/templates/native-go/backend/internal/helper/sdk_journal.go +77 -0
  244. package/dist/templates/native-go/backend/internal/helper/sdk_journal_linux.go +244 -0
  245. package/dist/templates/native-go/backend/internal/helper/sdk_journal_linux_test.go +295 -0
  246. package/dist/templates/native-go/backend/internal/helper/sdk_journal_test.go +71 -0
  247. package/dist/templates/native-go/backend/internal/helper/sdk_picker_test.go +50 -0
  248. package/dist/templates/native-go/backend/internal/helper/sdk_process_linux.go +184 -0
  249. package/dist/templates/native-go/backend/internal/helper/sdk_public_vm_linux_test.go +129 -0
  250. package/dist/templates/native-go/backend/internal/helper/sdk_relaxed_invalidation.go +242 -0
  251. package/dist/templates/native-go/backend/internal/helper/sdk_rpc.go +299 -0
  252. package/dist/templates/native-go/backend/internal/helper/sdk_rpc_contract.go +70 -0
  253. package/dist/templates/native-go/backend/internal/helper/sdk_rpc_test.go +135 -0
  254. package/dist/templates/native-go/backend/internal/helper/sdk_server_linux.go +161 -0
  255. package/dist/templates/native-go/backend/internal/helper/sdk_session.go +677 -0
  256. package/dist/templates/native-go/backend/internal/helper/sdk_session_contract.go +72 -0
  257. package/dist/templates/native-go/backend/internal/helper/sdk_session_fence_test.go +215 -0
  258. package/dist/templates/native-go/backend/internal/helper/sdk_session_test.go +475 -0
  259. package/dist/templates/native-go/backend/internal/helper/sdk_supervisor_frame.go +78 -0
  260. package/dist/templates/native-go/backend/internal/helper/sdk_supervisor_linux.go +414 -0
  261. package/dist/templates/native-go/backend/internal/helper/sdk_transport.go +79 -0
  262. package/dist/templates/native-go/backend/internal/helper/sdk_transport_linux.go +82 -0
  263. package/dist/templates/native-go/backend/internal/helper/sdk_transport_test.go +64 -0
  264. package/dist/templates/native-go/backend/internal/helper/server_linux.go +14 -2
  265. package/dist/templates/native-go/backend/internal/helper/stale_management_linux_test.go +126 -0
  266. package/dist/templates/native-go/backend/internal/helper/stale_management_test.go +122 -0
  267. package/dist/templates/native-go/backend/internal/helper/startup_diagnostic.go +68 -0
  268. package/dist/templates/native-go/backend/internal/helper/startup_diagnostic_linux_test.go +126 -0
  269. package/dist/templates/native-go/backend/internal/helper/startup_diagnostic_test.go +117 -0
  270. package/dist/templates/native-go/backend/internal/helper/status.go +22 -13
  271. package/dist/templates/native-go/backend/internal/helper/status_test.go +45 -0
  272. package/dist/templates/native-go/backend/internal/helper/supervision.go +1 -1
  273. package/dist/templates/native-go/backend/internal/helper/supervisor_frame.go +3 -0
  274. package/dist/templates/native-go/backend/internal/helper/supervisor_linux.go +6 -2
  275. package/dist/templates/native-go/backend/internal/helper/unit_connection_test.go +22 -4
  276. package/dist/templates/native-go/backend/internal/helper/unit_linux.go +7 -3
  277. package/dist/templates/native-go/backend/internal/helper/unit_query_linux.go +5 -1
  278. package/dist/templates/native-go/backend/internal/helper/write_files_linux.go +7 -4
  279. package/dist/templates/native-go/backend/internal/helper/write_files_linux_test.go +26 -0
  280. package/dist/templates/native-go/dsm-package.yaml +2 -2
  281. package/dist/templates/native-go-authorization/backend/internal/app/authorization.go +298 -0
  282. package/dist/templates/native-go-authorization/backend/internal/app/authorization_test.go +96 -0
  283. package/dist/templates/native-go-authorization/frontend/src/authorization/AuthorizationGate.vue +89 -0
  284. package/dist/templates/native-go-authorization/frontend/src/authorization/i18n/de.json +36 -0
  285. package/dist/templates/native-go-authorization/frontend/src/authorization/i18n/en.json +36 -0
  286. package/dist/templates/native-go-authorization/frontend/src/authorization/i18n/es.json +36 -0
  287. package/dist/templates/native-go-authorization/frontend/src/authorization/i18n/fr.json +36 -0
  288. package/dist/templates/native-go-authorization/frontend/src/authorization/i18n/index.ts +15 -0
  289. package/dist/templates/native-go-authorization/frontend/src/authorization/i18n/it.json +36 -0
  290. package/dist/templates/native-go-authorization/frontend/src/authorization/i18n/ja.json +36 -0
  291. package/dist/templates/native-go-authorization/frontend/src/authorization/i18n/ko.json +36 -0
  292. package/dist/templates/native-go-authorization/frontend/src/authorization/i18n/zh-CN.json +36 -0
  293. package/dist/templates/native-go-authorization/frontend/src/authorization/i18n/zh-TW.json +36 -0
  294. package/dist/templates/native-go-authorization/frontend/test/authorization-preview.html +14 -0
  295. package/dist/templates/native-go-authorization/frontend/test/authorization.test.ts +59 -0
  296. package/dist/templates/native-go-background/BACKGROUND.md +111 -0
  297. package/dist/templates/native-go-background/backend/internal/app/background_on.go +7 -0
  298. package/dist/templates/native-go-background/backend/internal/app/background_routes_test.go +32 -0
  299. package/dist/templates/native-go-background/frontend/src/background/BackgroundPanel.vue +178 -0
  300. package/dist/templates/native-go-background/frontend/src/background/i18n/de.json +83 -0
  301. package/dist/templates/native-go-background/frontend/src/background/i18n/en.json +83 -0
  302. package/dist/templates/native-go-background/frontend/src/background/i18n/es.json +83 -0
  303. package/dist/templates/native-go-background/frontend/src/background/i18n/fr.json +83 -0
  304. package/dist/templates/native-go-background/frontend/src/background/i18n/it.json +83 -0
  305. package/dist/templates/native-go-background/frontend/src/background/i18n/ja.json +83 -0
  306. package/dist/templates/native-go-background/frontend/src/background/i18n/ko.json +83 -0
  307. package/dist/templates/native-go-background/frontend/src/background/i18n/zh-CN.json +83 -0
  308. package/dist/templates/native-go-background/frontend/src/background/i18n/zh-TW.json +83 -0
  309. package/dist/templates/native-go-background/frontend/src/background/i18n.ts +19 -0
  310. package/dist/templates/native-go-background/frontend/src/background/picker-provider.ts +13 -0
  311. package/dist/templates/native-go-background/frontend/test/background.test.ts +179 -0
  312. package/dist/templates/native-go-integration/backend/internal/app/personal.go +2 -2
  313. package/dist/templates/native-go-integration/backend/internal/app/personal_test.go +37 -0
  314. package/dist/templates/native-go-vue/README.md +38 -7
  315. package/dist/templates/native-go-vue/frontend/DESIGN.md +15 -3
  316. package/dist/templates/native-go-vue/frontend/README-JOBS.md +75 -0
  317. package/dist/templates/native-go-vue/frontend/README-M2.md +64 -0
  318. package/dist/templates/native-go-vue/frontend/package-lock.json +5 -4
  319. package/dist/templates/native-go-vue/frontend/package.json +1 -1
  320. package/dist/templates/native-go-vue/frontend/src/App.vue +39 -11
  321. package/dist/templates/native-go-vue/frontend/src/M2App.vue +57 -0
  322. package/dist/templates/native-go-vue/frontend/src/SDKApp.vue +203 -0
  323. package/dist/templates/native-go-vue/frontend/src/context.ts +3 -0
  324. package/dist/templates/native-go-vue/frontend/src/jobs/InteractiveJobForm.vue +70 -0
  325. package/dist/templates/native-go-vue/frontend/src/jobs/JobsAdminPanel.vue +33 -0
  326. package/dist/templates/native-go-vue/frontend/src/jobs/JobsPanel.vue +135 -0
  327. package/dist/templates/native-go-vue/frontend/src/jobs/connected-provider.ts +179 -0
  328. package/dist/templates/native-go-vue/frontend/src/jobs/i18n/de.json +62 -0
  329. package/dist/templates/native-go-vue/frontend/src/jobs/i18n/en.json +62 -0
  330. package/dist/templates/native-go-vue/frontend/src/jobs/i18n/es.json +62 -0
  331. package/dist/templates/native-go-vue/frontend/src/jobs/i18n/fr.json +62 -0
  332. package/dist/templates/native-go-vue/frontend/src/jobs/i18n/it.json +62 -0
  333. package/dist/templates/native-go-vue/frontend/src/jobs/i18n/ja.json +62 -0
  334. package/dist/templates/native-go-vue/frontend/src/jobs/i18n/ko.json +62 -0
  335. package/dist/templates/native-go-vue/frontend/src/jobs/i18n/zh-CN.json +62 -0
  336. package/dist/templates/native-go-vue/frontend/src/jobs/i18n/zh-TW.json +62 -0
  337. package/dist/templates/native-go-vue/frontend/src/jobs/i18n.ts +15 -0
  338. package/dist/templates/native-go-vue/frontend/src/jobs/mock-provider.ts +67 -0
  339. package/dist/templates/native-go-vue/frontend/src/jobs/provider.ts +60 -0
  340. package/dist/templates/native-go-vue/frontend/src/jobs/validate.ts +44 -0
  341. package/dist/templates/native-go-vue/frontend/src/m2-main.ts +21 -0
  342. package/dist/templates/native-go-vue/frontend/src/m2-style.css +7 -0
  343. package/dist/templates/native-go-vue/frontend/src/nas/M2Files.vue +94 -0
  344. package/dist/templates/native-go-vue/frontend/src/nas/M2PathPicker.vue +70 -0
  345. package/dist/templates/native-go-vue/frontend/src/nas/M2Settings.vue +69 -0
  346. package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/de.json +56 -0
  347. package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/en.json +56 -0
  348. package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/es.json +56 -0
  349. package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/fr.json +56 -0
  350. package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/it.json +56 -0
  351. package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/ja.json +56 -0
  352. package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/ko.json +56 -0
  353. package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/zh-CN.json +56 -0
  354. package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/zh-TW.json +56 -0
  355. package/dist/templates/native-go-vue/frontend/src/nas/m2-contract.ts +33 -0
  356. package/dist/templates/native-go-vue/frontend/src/nas/m2-i18n.ts +24 -0
  357. package/dist/templates/native-go-vue/frontend/src/nas/m2-provider.ts +104 -0
  358. package/dist/templates/native-go-vue/frontend/src/nas/use-m2-request.ts +18 -0
  359. package/dist/templates/native-go-vue/frontend/src/sdk-main.ts +39 -0
  360. package/dist/templates/native-go-vue/frontend/src/shell/AppShell.vue +48 -0
  361. package/dist/templates/native-go-vue/frontend/src/shell/i18n/de.json +10 -0
  362. package/dist/templates/native-go-vue/frontend/src/shell/i18n/en.json +10 -0
  363. package/dist/templates/native-go-vue/frontend/src/shell/i18n/es.json +10 -0
  364. package/dist/templates/native-go-vue/frontend/src/shell/i18n/fr.json +10 -0
  365. package/dist/templates/native-go-vue/frontend/src/shell/i18n/it.json +10 -0
  366. package/dist/templates/native-go-vue/frontend/src/shell/i18n/ja.json +10 -0
  367. package/dist/templates/native-go-vue/frontend/src/shell/i18n/ko.json +10 -0
  368. package/dist/templates/native-go-vue/frontend/src/shell/i18n/zh-CN.json +10 -0
  369. package/dist/templates/native-go-vue/frontend/src/shell/i18n/zh-TW.json +10 -0
  370. package/dist/templates/native-go-vue/frontend/src/shell/i18n.ts +17 -0
  371. package/dist/templates/native-go-vue/frontend/src/shell/pages.ts +34 -0
  372. package/dist/templates/native-go-vue/frontend/src/style.css +2 -6
  373. package/dist/templates/native-go-vue/frontend/test/apps.test.ts +21 -0
  374. package/dist/templates/native-go-vue/frontend/test/jobs-connected.test.ts +163 -0
  375. package/dist/templates/native-go-vue/frontend/test/jobs-preview.html +22 -0
  376. package/dist/templates/native-go-vue/frontend/test/jobs.test.ts +176 -0
  377. package/dist/templates/native-go-vue/frontend/test/m2-preview.html +27 -0
  378. package/dist/templates/native-go-vue/frontend/test/m2-provider.test.ts +87 -0
  379. package/dist/templates/native-go-vue/frontend/test/m2-ui.test.ts +209 -0
  380. package/dist/templates/native-go-vue/frontend/test/native-controls.test.ts +27 -0
  381. package/dist/templates/native-go-vue/frontend/test/picker-wire.test.ts +110 -0
  382. package/dist/templates/native-go-vue/frontend/test/shell-preview.html +19 -0
  383. package/dist/templates/native-go-vue/frontend/test/shell-sdk.test.ts +73 -0
  384. package/dist/templates/native-go-vue/frontend/test/shell-ui.test.ts +114 -0
  385. package/dist/templates/native-go-vue/frontend/test/shell.test.ts +45 -0
  386. package/dist/templates/native-go-vue/frontend/vite.config.ts +2 -2
  387. package/dist/types.d.ts +1 -1
  388. package/dist/validate.js +6 -3
  389. package/package.json +5 -2
  390. package/schema/synology-app.schema.json +19 -0
  391. package/skills/reference/create.md +6 -2
  392. package/templates/_authorization/main.go +104 -0
  393. package/templates/_authorization/main_test.go +35 -0
  394. package/templates/_daemon/go.mod +2 -0
  395. package/templates/_gateway/auth.go +101 -0
  396. package/templates/_gateway/auth_test.go +216 -0
  397. package/templates/_gateway/cgi.go +133 -0
  398. package/templates/_gateway/cgi_test.go +156 -0
  399. package/templates/_gateway/csrf.go +73 -0
  400. package/templates/_gateway/csrf_test.go +155 -0
  401. package/templates/_gateway/directory.go +108 -0
  402. package/templates/_gateway/directory_test.go +202 -0
  403. package/templates/_gateway/gateway.go +412 -0
  404. package/templates/_gateway/gateway_test.go +676 -0
  405. package/templates/_gateway/go.mod +3 -0
  406. package/templates/_gateway/groups.go +66 -0
  407. package/templates/_gateway/main.go +45 -0
  408. package/templates/binary/synology-app.yaml +1 -1
  409. package/templates/compose-import/compose.yaml +1 -1
  410. package/templates/compose-import/synology-app.yaml +1 -1
  411. package/templates/hello-files/compose.yaml +1 -1
  412. package/templates/hello-files/synology-app.yaml +1 -1
  413. package/templates/native-go/README.md +118 -5
  414. package/templates/native-go/backend/THIRD_PARTY_NOTICES.txt +3 -1
  415. package/templates/native-go/backend/cmd/app/main.go +25 -6
  416. package/templates/native-go/backend/cmd/app/sdk_main.go +62 -0
  417. package/templates/native-go/backend/cmd/background-worker/main.go +38 -0
  418. package/templates/native-go/backend/cmd/helper/main.go +27 -1
  419. package/templates/native-go/backend/cmd/helper/sdk_registry.go +19 -0
  420. package/templates/native-go/backend/go.mod +5 -3
  421. package/templates/native-go/backend/go.sum +4 -4
  422. package/templates/native-go/backend/internal/api/registry.go +17 -6
  423. package/templates/native-go/backend/internal/api/server.go +7 -1
  424. package/templates/native-go/backend/internal/app/authorization_none.go +5 -0
  425. package/templates/native-go/backend/internal/app/background.go +270 -0
  426. package/templates/native-go/backend/internal/app/background_none.go +8 -0
  427. package/templates/native-go/backend/internal/app/background_owner.go +59 -0
  428. package/templates/native-go/backend/internal/app/background_owner_test.go +145 -0
  429. package/templates/native-go/backend/internal/app/background_schedule.go +74 -0
  430. package/templates/native-go/backend/internal/app/background_schedule_test.go +62 -0
  431. package/templates/native-go/backend/internal/app/background_test.go +346 -0
  432. package/templates/native-go/backend/internal/app/integration_state.go +16 -0
  433. package/templates/native-go/backend/internal/app/migrations.go +41 -0
  434. package/templates/native-go/backend/internal/app/migrations_test.go +41 -0
  435. package/templates/native-go/backend/internal/app/nas_cgi_artifact_test.go +8 -0
  436. package/templates/native-go/backend/internal/app/routes.go +22 -5
  437. package/templates/native-go/backend/internal/app/routes_test.go +20 -3
  438. package/templates/native-go/backend/internal/app/sdk_jobs.go +70 -0
  439. package/templates/native-go/backend/internal/app/sdk_jobs_test.go +116 -0
  440. package/templates/native-go/backend/internal/app/sdk_routes.go +177 -0
  441. package/templates/native-go/backend/internal/app/session_files.go +15 -0
  442. package/templates/native-go/backend/internal/app/session_files_test.go +26 -0
  443. package/templates/native-go/backend/internal/app/shared_folder.go +144 -0
  444. package/templates/native-go/backend/internal/app/shared_folder_test.go +158 -0
  445. package/templates/native-go/backend/internal/background/access.go +57 -0
  446. package/templates/native-go/backend/internal/background/owner_test.go +126 -0
  447. package/templates/native-go/backend/internal/background/runner.go +185 -0
  448. package/templates/native-go/backend/internal/background/runner_test.go +209 -0
  449. package/templates/native-go/backend/internal/background/schedule.go +300 -0
  450. package/templates/native-go/backend/internal/background/schedule_test.go +335 -0
  451. package/templates/native-go/backend/internal/background/store.go +807 -0
  452. package/templates/native-go/backend/internal/background/store_test.go +407 -0
  453. package/templates/native-go/backend/internal/datamigrate/migrate.go +191 -0
  454. package/templates/native-go/backend/internal/datamigrate/migrate_test.go +177 -0
  455. package/templates/native-go/backend/internal/filestation/browse_test.go +67 -0
  456. package/templates/native-go/backend/internal/filestation/client.go +251 -0
  457. package/templates/native-go/backend/internal/filestation/client_test.go +155 -0
  458. package/templates/native-go/backend/internal/filestation/doc.go +4 -0
  459. package/templates/native-go/backend/internal/filestation/read_file_test.go +90 -0
  460. package/templates/native-go/backend/internal/filestation/repository.go +207 -0
  461. package/templates/native-go/backend/internal/filestation/repository_test.go +104 -0
  462. package/templates/native-go/backend/internal/filestation/sdk_session.go +241 -0
  463. package/templates/native-go/backend/internal/filestation/sdk_session_test.go +183 -0
  464. package/templates/native-go/backend/internal/filestation/service.go +254 -0
  465. package/templates/native-go/backend/internal/filestation/service_test.go +94 -0
  466. package/templates/native-go/backend/internal/filestation/session_semantics_test.go +383 -0
  467. package/templates/native-go/backend/internal/filestation/write.go +72 -0
  468. package/templates/native-go/backend/internal/filestation/write_test.go +123 -0
  469. package/templates/native-go/backend/internal/helper/background_proof.go +163 -0
  470. package/templates/native-go/backend/internal/helper/background_proof_contract.go +46 -0
  471. package/templates/native-go/backend/internal/helper/background_proof_test.go +161 -0
  472. package/templates/native-go/backend/internal/helper/data_contract.go +3 -0
  473. package/templates/native-go/backend/internal/helper/data_protocol.go +3 -0
  474. package/templates/native-go/backend/internal/helper/data_protocol_test.go +1 -1
  475. package/templates/native-go/backend/internal/helper/grant_authority.go +3 -0
  476. package/templates/native-go/backend/internal/helper/grant_authority_test.go +1 -1
  477. package/templates/native-go/backend/internal/helper/grant_repository_linux.go +2 -1
  478. package/templates/native-go/backend/internal/helper/grant_repository_linux_test.go +4 -3
  479. package/templates/native-go/backend/internal/helper/grant_snapshot.go +3 -0
  480. package/templates/native-go/backend/internal/helper/jobcontract/bindings.go +48 -0
  481. package/templates/native-go/backend/internal/helper/jobcontract/contract_test.go +436 -0
  482. package/templates/native-go/backend/internal/helper/jobcontract/decode.go +185 -0
  483. package/templates/native-go/backend/internal/helper/jobcontract/digest.go +55 -0
  484. package/templates/native-go/backend/internal/helper/jobcontract/registry.go +85 -0
  485. package/templates/native-go/backend/internal/helper/jobcontract/registry_test.go +73 -0
  486. package/templates/native-go/backend/internal/helper/jobcontract/result.go +53 -0
  487. package/templates/native-go/backend/internal/helper/jobcontract/stub.go +8 -0
  488. package/templates/native-go/backend/internal/helper/jobcontract/testdata/input-digests.json +127 -0
  489. package/templates/native-go/backend/internal/helper/jobcontract/types.go +234 -0
  490. package/templates/native-go/backend/internal/helper/jobcontract/types_test.go +29 -0
  491. package/templates/native-go/backend/internal/helper/jobcontract/validate.go +462 -0
  492. package/templates/native-go/backend/internal/helper/legacy_authority_schema_linux_test.go +34 -0
  493. package/templates/native-go/backend/internal/helper/legacy_authority_schema_test.go +6 -0
  494. package/templates/native-go/backend/internal/helper/management.go +86 -0
  495. package/templates/native-go/backend/internal/helper/observation.go +42 -2
  496. package/templates/native-go/backend/internal/helper/observation_health_test.go +13 -1
  497. package/templates/native-go/backend/internal/helper/observer_diagnostic.go +56 -10
  498. package/templates/native-go/backend/internal/helper/observer_diagnostic_linux_test.go +6 -0
  499. package/templates/native-go/backend/internal/helper/observer_diagnostic_writer.go +40 -0
  500. package/templates/native-go/backend/internal/helper/process_linux.go +3 -0
  501. package/templates/native-go/backend/internal/helper/purge.go +115 -0
  502. package/templates/native-go/backend/internal/helper/purge_test.go +59 -0
  503. package/templates/native-go/backend/internal/helper/query_deadline_linux_test.go +115 -0
  504. package/templates/native-go/backend/internal/helper/query_pending_diagnostic_test.go +254 -0
  505. package/templates/native-go/backend/internal/helper/relaxed_invalidation.go +3 -0
  506. package/templates/native-go/backend/internal/helper/revoke.go +109 -0
  507. package/templates/native-go/backend/internal/helper/revoke_test.go +121 -0
  508. package/templates/native-go/backend/internal/helper/s1r_actor_races_linux_test.go +23 -4
  509. package/templates/native-go/backend/internal/helper/sdk_assembly_linux.go +51 -0
  510. package/templates/native-go/backend/internal/helper/sdk_attach.go +83 -0
  511. package/templates/native-go/backend/internal/helper/sdk_attach_contract.go +32 -0
  512. package/templates/native-go/backend/internal/helper/sdk_attach_test.go +184 -0
  513. package/templates/native-go/backend/internal/helper/sdk_authority_schema_test.go +6 -0
  514. package/templates/native-go/backend/internal/helper/sdk_background_authority.go +832 -0
  515. package/templates/native-go/backend/internal/helper/sdk_background_authority_test.go +714 -0
  516. package/templates/native-go/backend/internal/helper/sdk_background_invalidation.go +218 -0
  517. package/templates/native-go/backend/internal/helper/sdk_background_lease.go +218 -0
  518. package/templates/native-go/backend/internal/helper/sdk_background_snapshot.go +110 -0
  519. package/templates/native-go/backend/internal/helper/sdk_background_store_linux_test.go +261 -0
  520. package/templates/native-go/backend/internal/helper/sdk_background_store_test.go +58 -0
  521. package/templates/native-go/backend/internal/helper/sdk_bindings.go +60 -0
  522. package/templates/native-go/backend/internal/helper/sdk_data_contract.go +251 -0
  523. package/templates/native-go/backend/internal/helper/sdk_data_protocol.go +424 -0
  524. package/templates/native-go/backend/internal/helper/sdk_dispatch_absence_test.go +51 -0
  525. package/templates/native-go/backend/internal/helper/sdk_display.go +488 -0
  526. package/templates/native-go/backend/internal/helper/sdk_display_contract.go +121 -0
  527. package/templates/native-go/backend/internal/helper/sdk_display_test.go +459 -0
  528. package/templates/native-go/backend/internal/helper/sdk_grant_authority.go +559 -0
  529. package/templates/native-go/backend/internal/helper/sdk_grant_repository_linux.go +417 -0
  530. package/templates/native-go/backend/internal/helper/sdk_grant_snapshot.go +94 -0
  531. package/templates/native-go/backend/internal/helper/sdk_history.go +80 -0
  532. package/templates/native-go/backend/internal/helper/sdk_history_test.go +76 -0
  533. package/templates/native-go/backend/internal/helper/sdk_job.go +697 -0
  534. package/templates/native-go/backend/internal/helper/sdk_job_child_linux.go +309 -0
  535. package/templates/native-go/backend/internal/helper/sdk_job_contract.go +122 -0
  536. package/templates/native-go/backend/internal/helper/sdk_job_linux_test.go +159 -0
  537. package/templates/native-go/backend/internal/helper/sdk_job_process_linux.go +327 -0
  538. package/templates/native-go/backend/internal/helper/sdk_job_test.go +501 -0
  539. package/templates/native-go/backend/internal/helper/sdk_job_vm_linux_test.go +343 -0
  540. package/templates/native-go/backend/internal/helper/sdk_journal.go +77 -0
  541. package/templates/native-go/backend/internal/helper/sdk_journal_linux.go +244 -0
  542. package/templates/native-go/backend/internal/helper/sdk_journal_linux_test.go +295 -0
  543. package/templates/native-go/backend/internal/helper/sdk_journal_test.go +71 -0
  544. package/templates/native-go/backend/internal/helper/sdk_picker_test.go +50 -0
  545. package/templates/native-go/backend/internal/helper/sdk_process_linux.go +184 -0
  546. package/templates/native-go/backend/internal/helper/sdk_public_vm_linux_test.go +129 -0
  547. package/templates/native-go/backend/internal/helper/sdk_relaxed_invalidation.go +242 -0
  548. package/templates/native-go/backend/internal/helper/sdk_rpc.go +299 -0
  549. package/templates/native-go/backend/internal/helper/sdk_rpc_contract.go +70 -0
  550. package/templates/native-go/backend/internal/helper/sdk_rpc_test.go +135 -0
  551. package/templates/native-go/backend/internal/helper/sdk_server_linux.go +161 -0
  552. package/templates/native-go/backend/internal/helper/sdk_session.go +677 -0
  553. package/templates/native-go/backend/internal/helper/sdk_session_contract.go +72 -0
  554. package/templates/native-go/backend/internal/helper/sdk_session_fence_test.go +215 -0
  555. package/templates/native-go/backend/internal/helper/sdk_session_test.go +475 -0
  556. package/templates/native-go/backend/internal/helper/sdk_supervisor_frame.go +78 -0
  557. package/templates/native-go/backend/internal/helper/sdk_supervisor_linux.go +414 -0
  558. package/templates/native-go/backend/internal/helper/sdk_transport.go +79 -0
  559. package/templates/native-go/backend/internal/helper/sdk_transport_linux.go +82 -0
  560. package/templates/native-go/backend/internal/helper/sdk_transport_test.go +64 -0
  561. package/templates/native-go/backend/internal/helper/server_linux.go +14 -2
  562. package/templates/native-go/backend/internal/helper/stale_management_linux_test.go +126 -0
  563. package/templates/native-go/backend/internal/helper/stale_management_test.go +122 -0
  564. package/templates/native-go/backend/internal/helper/startup_diagnostic.go +68 -0
  565. package/templates/native-go/backend/internal/helper/startup_diagnostic_linux_test.go +126 -0
  566. package/templates/native-go/backend/internal/helper/startup_diagnostic_test.go +117 -0
  567. package/templates/native-go/backend/internal/helper/status.go +22 -13
  568. package/templates/native-go/backend/internal/helper/status_test.go +45 -0
  569. package/templates/native-go/backend/internal/helper/supervision.go +1 -1
  570. package/templates/native-go/backend/internal/helper/supervisor_frame.go +3 -0
  571. package/templates/native-go/backend/internal/helper/supervisor_linux.go +6 -2
  572. package/templates/native-go/backend/internal/helper/unit_connection_test.go +22 -4
  573. package/templates/native-go/backend/internal/helper/unit_linux.go +7 -3
  574. package/templates/native-go/backend/internal/helper/unit_query_linux.go +5 -1
  575. package/templates/native-go/backend/internal/helper/write_files_linux.go +7 -4
  576. package/templates/native-go/backend/internal/helper/write_files_linux_test.go +26 -0
  577. package/templates/native-go/dsm-package.yaml +2 -2
  578. package/templates/native-go-authorization/backend/internal/app/authorization.go +298 -0
  579. package/templates/native-go-authorization/backend/internal/app/authorization_test.go +96 -0
  580. package/templates/native-go-authorization/frontend/src/authorization/AuthorizationGate.vue +89 -0
  581. package/templates/native-go-authorization/frontend/src/authorization/i18n/de.json +36 -0
  582. package/templates/native-go-authorization/frontend/src/authorization/i18n/en.json +36 -0
  583. package/templates/native-go-authorization/frontend/src/authorization/i18n/es.json +36 -0
  584. package/templates/native-go-authorization/frontend/src/authorization/i18n/fr.json +36 -0
  585. package/templates/native-go-authorization/frontend/src/authorization/i18n/index.ts +15 -0
  586. package/templates/native-go-authorization/frontend/src/authorization/i18n/it.json +36 -0
  587. package/templates/native-go-authorization/frontend/src/authorization/i18n/ja.json +36 -0
  588. package/templates/native-go-authorization/frontend/src/authorization/i18n/ko.json +36 -0
  589. package/templates/native-go-authorization/frontend/src/authorization/i18n/zh-CN.json +36 -0
  590. package/templates/native-go-authorization/frontend/src/authorization/i18n/zh-TW.json +36 -0
  591. package/templates/native-go-authorization/frontend/test/authorization-preview.html +14 -0
  592. package/templates/native-go-authorization/frontend/test/authorization.test.ts +59 -0
  593. package/templates/native-go-background/BACKGROUND.md +111 -0
  594. package/templates/native-go-background/backend/internal/app/background_on.go +7 -0
  595. package/templates/native-go-background/backend/internal/app/background_routes_test.go +32 -0
  596. package/templates/native-go-background/frontend/src/background/BackgroundPanel.vue +178 -0
  597. package/templates/native-go-background/frontend/src/background/i18n/de.json +83 -0
  598. package/templates/native-go-background/frontend/src/background/i18n/en.json +83 -0
  599. package/templates/native-go-background/frontend/src/background/i18n/es.json +83 -0
  600. package/templates/native-go-background/frontend/src/background/i18n/fr.json +83 -0
  601. package/templates/native-go-background/frontend/src/background/i18n/it.json +83 -0
  602. package/templates/native-go-background/frontend/src/background/i18n/ja.json +83 -0
  603. package/templates/native-go-background/frontend/src/background/i18n/ko.json +83 -0
  604. package/templates/native-go-background/frontend/src/background/i18n/zh-CN.json +83 -0
  605. package/templates/native-go-background/frontend/src/background/i18n/zh-TW.json +83 -0
  606. package/templates/native-go-background/frontend/src/background/i18n.ts +19 -0
  607. package/templates/native-go-background/frontend/src/background/picker-provider.ts +13 -0
  608. package/templates/native-go-background/frontend/test/background.test.ts +179 -0
  609. package/templates/native-go-integration/backend/internal/app/personal.go +2 -2
  610. package/templates/native-go-integration/backend/internal/app/personal_test.go +37 -0
  611. package/templates/native-go-vue/README.md +38 -7
  612. package/templates/native-go-vue/frontend/DESIGN.md +15 -3
  613. package/templates/native-go-vue/frontend/README-JOBS.md +75 -0
  614. package/templates/native-go-vue/frontend/README-M2.md +64 -0
  615. package/templates/native-go-vue/frontend/package-lock.json +5 -4
  616. package/templates/native-go-vue/frontend/package.json +1 -1
  617. package/templates/native-go-vue/frontend/src/App.vue +39 -11
  618. package/templates/native-go-vue/frontend/src/M2App.vue +57 -0
  619. package/templates/native-go-vue/frontend/src/SDKApp.vue +203 -0
  620. package/templates/native-go-vue/frontend/src/context.ts +3 -0
  621. package/templates/native-go-vue/frontend/src/jobs/InteractiveJobForm.vue +70 -0
  622. package/templates/native-go-vue/frontend/src/jobs/JobsAdminPanel.vue +33 -0
  623. package/templates/native-go-vue/frontend/src/jobs/JobsPanel.vue +135 -0
  624. package/templates/native-go-vue/frontend/src/jobs/connected-provider.ts +179 -0
  625. package/templates/native-go-vue/frontend/src/jobs/i18n/de.json +62 -0
  626. package/templates/native-go-vue/frontend/src/jobs/i18n/en.json +62 -0
  627. package/templates/native-go-vue/frontend/src/jobs/i18n/es.json +62 -0
  628. package/templates/native-go-vue/frontend/src/jobs/i18n/fr.json +62 -0
  629. package/templates/native-go-vue/frontend/src/jobs/i18n/it.json +62 -0
  630. package/templates/native-go-vue/frontend/src/jobs/i18n/ja.json +62 -0
  631. package/templates/native-go-vue/frontend/src/jobs/i18n/ko.json +62 -0
  632. package/templates/native-go-vue/frontend/src/jobs/i18n/zh-CN.json +62 -0
  633. package/templates/native-go-vue/frontend/src/jobs/i18n/zh-TW.json +62 -0
  634. package/templates/native-go-vue/frontend/src/jobs/i18n.ts +15 -0
  635. package/templates/native-go-vue/frontend/src/jobs/mock-provider.ts +67 -0
  636. package/templates/native-go-vue/frontend/src/jobs/provider.ts +60 -0
  637. package/templates/native-go-vue/frontend/src/jobs/validate.ts +44 -0
  638. package/templates/native-go-vue/frontend/src/m2-main.ts +21 -0
  639. package/templates/native-go-vue/frontend/src/m2-style.css +7 -0
  640. package/templates/native-go-vue/frontend/src/nas/M2Files.vue +94 -0
  641. package/templates/native-go-vue/frontend/src/nas/M2PathPicker.vue +70 -0
  642. package/templates/native-go-vue/frontend/src/nas/M2Settings.vue +69 -0
  643. package/templates/native-go-vue/frontend/src/nas/i18n/m2/de.json +56 -0
  644. package/templates/native-go-vue/frontend/src/nas/i18n/m2/en.json +56 -0
  645. package/templates/native-go-vue/frontend/src/nas/i18n/m2/es.json +56 -0
  646. package/templates/native-go-vue/frontend/src/nas/i18n/m2/fr.json +56 -0
  647. package/templates/native-go-vue/frontend/src/nas/i18n/m2/it.json +56 -0
  648. package/templates/native-go-vue/frontend/src/nas/i18n/m2/ja.json +56 -0
  649. package/templates/native-go-vue/frontend/src/nas/i18n/m2/ko.json +56 -0
  650. package/templates/native-go-vue/frontend/src/nas/i18n/m2/zh-CN.json +56 -0
  651. package/templates/native-go-vue/frontend/src/nas/i18n/m2/zh-TW.json +56 -0
  652. package/templates/native-go-vue/frontend/src/nas/m2-contract.ts +33 -0
  653. package/templates/native-go-vue/frontend/src/nas/m2-i18n.ts +24 -0
  654. package/templates/native-go-vue/frontend/src/nas/m2-provider.ts +104 -0
  655. package/templates/native-go-vue/frontend/src/nas/use-m2-request.ts +18 -0
  656. package/templates/native-go-vue/frontend/src/sdk-main.ts +39 -0
  657. package/templates/native-go-vue/frontend/src/shell/AppShell.vue +48 -0
  658. package/templates/native-go-vue/frontend/src/shell/i18n/de.json +10 -0
  659. package/templates/native-go-vue/frontend/src/shell/i18n/en.json +10 -0
  660. package/templates/native-go-vue/frontend/src/shell/i18n/es.json +10 -0
  661. package/templates/native-go-vue/frontend/src/shell/i18n/fr.json +10 -0
  662. package/templates/native-go-vue/frontend/src/shell/i18n/it.json +10 -0
  663. package/templates/native-go-vue/frontend/src/shell/i18n/ja.json +10 -0
  664. package/templates/native-go-vue/frontend/src/shell/i18n/ko.json +10 -0
  665. package/templates/native-go-vue/frontend/src/shell/i18n/zh-CN.json +10 -0
  666. package/templates/native-go-vue/frontend/src/shell/i18n/zh-TW.json +10 -0
  667. package/templates/native-go-vue/frontend/src/shell/i18n.ts +17 -0
  668. package/templates/native-go-vue/frontend/src/shell/pages.ts +34 -0
  669. package/templates/native-go-vue/frontend/src/style.css +2 -6
  670. package/templates/native-go-vue/frontend/test/apps.test.ts +21 -0
  671. package/templates/native-go-vue/frontend/test/jobs-connected.test.ts +163 -0
  672. package/templates/native-go-vue/frontend/test/jobs-preview.html +22 -0
  673. package/templates/native-go-vue/frontend/test/jobs.test.ts +176 -0
  674. package/templates/native-go-vue/frontend/test/m2-preview.html +27 -0
  675. package/templates/native-go-vue/frontend/test/m2-provider.test.ts +87 -0
  676. package/templates/native-go-vue/frontend/test/m2-ui.test.ts +209 -0
  677. package/templates/native-go-vue/frontend/test/native-controls.test.ts +27 -0
  678. package/templates/native-go-vue/frontend/test/picker-wire.test.ts +110 -0
  679. package/templates/native-go-vue/frontend/test/shell-preview.html +19 -0
  680. package/templates/native-go-vue/frontend/test/shell-sdk.test.ts +73 -0
  681. package/templates/native-go-vue/frontend/test/shell-ui.test.ts +114 -0
  682. package/templates/native-go-vue/frontend/test/shell.test.ts +45 -0
  683. package/templates/native-go-vue/frontend/vite.config.ts +2 -2
  684. package/dist/templates/hello-web/compose.yaml +0 -21
  685. package/dist/templates/hello-web/html/app.js +0 -1
  686. package/dist/templates/hello-web/html/index.html +0 -13
  687. package/dist/templates/hello-web/synology-app.yaml +0 -13
  688. package/dist/templates/hello-web-page/compose.yaml +0 -21
  689. package/dist/templates/hello-web-page/html/app.js +0 -1
  690. package/dist/templates/hello-web-page/html/index.html +0 -13
  691. package/dist/templates/hello-web-page/synology-app.yaml +0 -13
  692. package/templates/hello-web/compose.yaml +0 -21
  693. package/templates/hello-web/html/app.js +0 -1
  694. package/templates/hello-web/html/index.html +0 -13
  695. package/templates/hello-web/synology-app.yaml +0 -13
  696. package/templates/hello-web-page/compose.yaml +0 -21
  697. package/templates/hello-web-page/html/app.js +0 -1
  698. package/templates/hello-web-page/html/index.html +0 -13
  699. package/templates/hello-web-page/synology-app.yaml +0 -13
@@ -0,0 +1,209 @@
1
+ // @vitest-environment jsdom
2
+ import {expect,test,vi} from 'vitest'
3
+ import {createApp,h,nextTick} from 'vue'
4
+ import {AppRoot} from '@synopackageland/ui'
5
+ import {mount} from '../src/m2-main'
6
+ import M2PathPicker from '../src/nas/M2PathPicker.vue'
7
+ import {M2_MAX_BYTES,M2ProviderKey,type M2Provider} from '../src/nas/m2-contract'
8
+ import {m2Copy,m2Catalogs,m2ErrorCopy} from '../src/nas/m2-i18n'
9
+ import {format} from '../src/nas/i18n'
10
+ import {shellCopy} from '../src/shell/i18n'
11
+ import type {Context} from '../src/context'
12
+ vi.stubGlobal('URL',Object.assign(URL,{createObjectURL:vi.fn(()=> 'blob:local-test'),revokeObjectURL:vi.fn()}))
13
+ const id='a'.repeat(64)
14
+ async function settle(){for(let i=0;i<20;i++){await Promise.resolve();await nextTick()}await new Promise(r=>setTimeout(r,25))}
15
+ function click(root:HTMLElement,text:string){const b=[...root.querySelectorAll('button')].find(b=>b.textContent?.trim()===text||b.getAttribute('aria-label')===text);expect(b,text).toBeTruthy();b!.click()}
16
+ function fixture(locale='en'){
17
+ let user='normal1',selections:any[]=[],failure=''
18
+ const reopen:Array<()=>void>=[]
19
+ const call=vi.fn(async(method:string,args?:Record<string,unknown>)=>{
20
+ if(failure&&method.startsWith('nas.'))throw {code:failure,message:'Cookie=secret /private'}
21
+ if(method==='app.status')return {package:'SamePackage',version:'1',user,admin:false}
22
+ if(method==='nas.session.status')return {mode:'filestation-session',interactive:'session-required',background:'unavailable',validation:'local-only'}
23
+ if(method==='nas.discovery')return {files:args?.folder?[]:null,shares:args?.folder?undefined:[{name:'Home',path:'/home',isdir:true}],total:args?.folder?0:1,offset:0}
24
+ if(method==='nas.grants.list')return selections
25
+ if(method==='nas.grants.confirm'){const s={grantId:id,folder:args!.folder,writable:args!.writable};selections.push(s);return s}
26
+ if(method==='nas.grants.revoke'){selections=[];return {revoked:true}}
27
+ if(method==='nas.files.list')return {files:[{name:'notes.txt',path:'/home/notes.txt',isdir:false,additional:{size:3}}],total:1,offset:0}
28
+ if(method==='nas.files.stat')return {name:'notes.txt',path:'/home/notes.txt',isdir:false,additional:{size:3}}
29
+ if(method==='nas.files.read')return {data:'YWJj',bytes:3}
30
+ if(method==='nas.files.create')return {created:true}
31
+ if(method==='nas.files.write')return {written:true}
32
+ throw {code:'unknown_method'}
33
+ })
34
+ const ctx={locale,call,reopen(fn:()=>void){reopen.push(fn);return ()=>{}}} as Context
35
+ const root=document.createElement('div');document.body.append(root);const app=mount(root,ctx)
36
+ return {root,app,call,reopen,setUser(value:string){user=value},fail(code:string){failure=code},close(){app.unmount();root.remove()}}
37
+ }
38
+ test('the File Station build uses the same DSM sidebar with a status page',async()=>{
39
+ const f=fixture(),copy=shellCopy('en')
40
+ try{
41
+ await settle()
42
+ const labels=[...f.root.querySelectorAll<HTMLElement>('[data-nav-item]')].map(node=>node.textContent?.trim())
43
+ expect(labels).toEqual([copy.status,copy.folders])
44
+ expect(f.root.querySelector('h1')?.textContent?.trim()).toBe(copy.status)
45
+ const folders=[...f.root.querySelectorAll<HTMLElement>('[data-nav-item]')].find(node=>node.textContent?.trim()===copy.folders)!
46
+ folders.click();await settle()
47
+ expect(f.root.querySelector('h1')?.textContent?.trim()).toBe(copy.folders)
48
+ expect(f.root.textContent).toContain(m2Copy('en').ownPermissions)
49
+ }finally{f.close()}
50
+ })
51
+
52
+ test('real session routes drive browse, writable consent, file actions and revoke without helper approval',async()=>{
53
+ const f=fixture(),copy=m2Copy('en')
54
+ try{
55
+ await settle();expect(f.root.textContent).toContain(copy.ownPermissions);expect(f.root.textContent).toContain(copy.unsupported)
56
+ click(f.root,copy.choose);await settle();click(f.root,'Select Home');await settle()
57
+ const checkbox=f.root.querySelector<HTMLElement>('[role=checkbox]')!;checkbox.click()
58
+ click(f.root,copy.confirm);await settle()
59
+ expect(f.call).toHaveBeenCalledWith('nas.grants.confirm',{folder:'/home',writable:true})
60
+ click(f.root,copy.openFiles);await settle();click(f.root,copy.inspect);await settle()
61
+ expect(f.root.textContent).toContain('3');click(f.root,copy.read);await settle()
62
+ expect(f.root.textContent).toContain(copy.readComplete)
63
+ const labelled=(text:string)=>f.root.querySelector<HTMLElement>(`#${[...f.root.querySelectorAll('label')].find(l=>l.textContent?.startsWith(text))!.htmlFor}`)
64
+ expect(labelled(copy.folderName)).toBe(f.root.querySelector('input[data-folder-name]'));expect(labelled(copy.uploadFile)).toBe(f.root.querySelector('input[type=file]'))
65
+ const input=f.root.querySelector<HTMLInputElement>('input[data-folder-name]')!;input.value='new';input.dispatchEvent(new Event('input',{bubbles:true}));await settle()
66
+ click(f.root,copy.create);await settle();expect(f.call).toHaveBeenCalledWith('nas.files.create',{grantId:id,relative:'',name:'new'})
67
+ const upload=f.root.querySelector<HTMLInputElement>('input[type=file]')!
68
+ Object.defineProperty(upload,'files',{value:[{name:'note.bin',size:3,arrayBuffer:async()=>new Uint8Array([0,1,255]).buffer}]})
69
+ upload.dispatchEvent(new Event('change',{bubbles:true}));await settle();click(f.root,copy.upload);await settle()
70
+ expect(f.call).toHaveBeenCalledWith('nas.files.write',{grantId:id,relative:'',name:'note.bin',data:'AAH/'})
71
+ click(f.root,copy.revoke);await settle();click(f.root,copy.confirmRevoke);await settle()
72
+ expect(f.root.textContent).toContain(copy.emptySelections)
73
+ expect(f.call.mock.calls.some(([m])=>/prepare|stream|publish|integration|root/.test(m))).toBe(false)
74
+ }finally{f.close()}
75
+ })
76
+ test('nine catalogs contain matching placeholders and distinct recovery copy for each safe failure',async()=>{
77
+ expect(Object.keys(m2Catalogs)).toHaveLength(9)
78
+ for(const [locale,copy]of Object.entries(m2Catalogs)){
79
+ expect(Object.keys(copy).sort()).toEqual(Object.keys(m2Catalogs.en).sort())
80
+ for(const key of Object.keys(copy)as Array<keyof typeof copy>){expect(copy[key].trim()).not.toBe('');expect(copy[key].match(/\{\w+\}/g)??[]).toEqual(m2Catalogs.en[key].match(/\{\w+\}/g)??[])}
81
+ const messages=['session_required','denied','not_found','conflict','commit-uncertain'].map(code=>m2ErrorCopy({code,message:'secret'},copy))
82
+ expect(new Set(messages).size).toBe(5)
83
+ const f=fixture(locale)
84
+ try{await settle();expect(f.root.textContent).toContain(copy.ownPermissions);f.fail('session_required');click(f.root,copy.refresh);await settle();expect(f.root.textContent).toContain(copy.errorSession);expect(f.root.textContent).not.toContain('Cookie=secret')}
85
+ finally{f.close()}
86
+ }
87
+ })
88
+ test('account change discards selections and pending dialogs until remount; two mounts isolate dialogs',async()=>{
89
+ const a=fixture(),b=fixture(),copy=m2Copy('en')
90
+ try{
91
+ await settle();click(a.root,copy.choose);click(b.root,copy.choose);await settle()
92
+ const ids=[...document.querySelectorAll('[id]')].map(n=>n.id).filter(Boolean);expect(new Set(ids).size).toBe(ids.length)
93
+ expect(a.root.querySelector('[role=dialog]')?.hasAttribute('aria-modal')).toBe(false)
94
+ a.setUser('normal2');a.reopen.forEach(fn=>fn());await settle()
95
+ expect(a.root.textContent).toContain(copy.accountChanged);expect(a.root.querySelector('[role=dialog]')).toBeNull()
96
+ expect(b.root.querySelector('[role=dialog]')).toBeTruthy()
97
+ }finally{a.close();b.close()}
98
+ })
99
+
100
+ test('read-only selection hides mutations; cancellation never confirms and errors never auto-retry',async()=>{
101
+ const f=fixture(),copy=m2Copy('en')
102
+ try{
103
+ await settle();click(f.root,copy.choose);await settle();click(f.root,'Select Home');click(f.root,copy.cancel);await settle()
104
+ expect(f.call.mock.calls.some(([m])=>m==='nas.grants.confirm')).toBe(false)
105
+ click(f.root,copy.choose);await settle();click(f.root,'Select Home');await settle();click(f.root,copy.confirm);await settle()
106
+ expect(f.call).toHaveBeenCalledWith('nas.grants.confirm',{folder:'/home',writable:false})
107
+ click(f.root,copy.openFiles);await settle()
108
+ expect(f.root.querySelector('input[type=file]')).toBeNull();expect(f.root.querySelector('input[data-folder-name]')).toBeNull()
109
+ f.fail('not_found');click(f.root,copy.inspect);await settle();expect(f.root.textContent).toContain(copy.errorNotFound)
110
+ expect(f.call.mock.calls.filter(([m])=>m==='nas.files.stat')).toHaveLength(1)
111
+ }finally{f.close()}
112
+ })
113
+ test('late discovery is discarded on unmount; a failed refresh does not forget the original account binding',async()=>{
114
+ const f=fixture(),copy=m2Copy('en')
115
+ try{
116
+ await settle()
117
+ const original=f.call.getMockImplementation()!
118
+ f.call.mockImplementation(async(method,args)=>{if(method==='app.status')throw {code:'session_required'};return original(method,args)})
119
+ f.reopen.forEach(fn=>fn());await settle()
120
+ f.call.mockImplementation(original);f.setUser('normal2');f.reopen.forEach(fn=>fn());await settle()
121
+ expect(f.root.textContent).toContain(copy.accountChanged)
122
+ }finally{f.close()}
123
+ const g=fixture()
124
+ await settle()
125
+ let release!:(value:unknown)=>void
126
+ const original=g.call.getMockImplementation()!
127
+ g.call.mockImplementation(async(method,args)=>method==='nas.discovery'?new Promise(r=>release=r):original(method,args))
128
+ click(g.root,copy.choose);await settle();g.app.unmount()
129
+ release({files:null,shares:[{name:'private',path:'/private',isdir:true}],total:1,offset:0});await settle()
130
+ expect(g.root.childElementCount).toBe(0);g.root.remove()
131
+ })
132
+
133
+ test('pending upload blocks competing folder controls and duplicate submission; uncertain outcome is actionable',async()=>{
134
+ const f=fixture(),copy=m2Copy('en')
135
+ try{
136
+ await settle();click(f.root,copy.choose);await settle();click(f.root,'Select Home');await settle()
137
+ const checkbox=f.root.querySelector<HTMLElement>('[role=checkbox]')!;checkbox.click()
138
+ click(f.root,copy.confirm);await settle();click(f.root,copy.openFiles);await settle()
139
+ const upload=f.root.querySelector<HTMLInputElement>('input[type=file]')!
140
+ Object.defineProperty(upload,'files',{value:[{name:'note.bin',size:3,arrayBuffer:async()=>new Uint8Array([0,1,255]).buffer}]})
141
+ upload.dispatchEvent(new Event('change',{bubbles:true}));await settle()
142
+ let reject!:(error:unknown)=>void
143
+ const original=f.call.getMockImplementation()!
144
+ f.call.mockImplementation(async(method,args)=>method==='nas.files.write'?new Promise((_resolve,r)=>reject=r):original(method,args))
145
+ click(f.root,copy.upload);click(f.root,copy.upload);await settle()
146
+ for(const button of [...f.root.querySelectorAll('button')].filter(b=>[copy.choose,copy.revoke,copy.refresh,copy.openFiles].includes(b.textContent?.trim()??'')))expect(button.disabled).toBe(true)
147
+ reject({code:'commit-uncertain'});await settle()
148
+ expect(f.root.textContent).toContain(copy.errorUncertain)
149
+ expect(f.call.mock.calls.filter(([m])=>m==='nas.files.write')).toHaveLength(1)
150
+ expect([...f.root.querySelectorAll('button')].find(b=>b.textContent?.trim()===copy.upload)?.disabled).toBe(true)
151
+ }finally{f.close()}
152
+ })
153
+
154
+ function mountPicker(consent?:string){
155
+ const provider={discovery:async()=>({entries:[],nextOffset:undefined})} as unknown as M2Provider
156
+ const root=document.createElement('div');document.body.append(root)
157
+ const app=createApp({render:()=>h(AppRoot,{},()=>h(M2PathPicker,{open:true,locale:'en',consent}))})
158
+ app.provide(M2ProviderKey,provider);app.config.idPrefix=`picker-${Math.random()}`;app.mount(root)
159
+ return {root,close(){app.unmount();root.remove()}}
160
+ }
161
+ test('the picker shows the caller-provided consent copy and keeps the M2 copy by default',async()=>{
162
+ const custom=mountPicker('Read once with your DSM login.');await settle()
163
+ expect(custom.root.textContent).toContain('Read once with your DSM login.');expect(custom.root.textContent).not.toContain(m2Copy('en').consent);custom.close()
164
+ const plain=mountPicker();await settle()
165
+ expect(plain.root.textContent).toContain(m2Copy('en').consent);plain.close()
166
+ })
167
+
168
+ async function openFiles(f:ReturnType<typeof fixture>,locale='en'){
169
+ const copy=m2Copy(locale)
170
+ await settle()
171
+ // In ko the navigation item shares the Choose button's label; the page button is the last match.
172
+ ;[...f.root.querySelectorAll('button')].filter(b=>b.textContent?.trim()===copy.choose).at(-1)!.click();await settle();click(f.root,format(copy.select,'Home'));await settle()
173
+ f.root.querySelector<HTMLElement>('[role=checkbox]')!.click()
174
+ click(f.root,copy.confirm);await settle();click(f.root,copy.openFiles);await settle()
175
+ return copy
176
+ }
177
+ // UI FilePicker "Choose files" per locale, written as escapes: template sources keep non-English text in catalogs only.
178
+ const chooseFiles:Record<string,string>={en:'Choose files','zh-TW':'\u9078\u64c7\u6a94\u6848','zh-CN':'\u9009\u62e9\u6587\u4ef6',ja:'\u30d5\u30a1\u30a4\u30eb\u3092\u9078\u629e',ko:'\ud30c\uc77c \uc120\ud0dd',de:'Dateien ausw\u00e4hlen',fr:'Choisir des fichiers',es:'Elegir archivos',it:'Scegli file'}
179
+ test.each(Object.keys(chooseFiles))('upload uses the compact UI FilePicker in %s',async locale=>{
180
+ const f=fixture(locale)
181
+ try{
182
+ const copy=await openFiles(f,locale)
183
+ expect(f.root.textContent).toContain(chooseFiles[locale])
184
+ expect(f.root.querySelectorAll('input[type=file]')).toHaveLength(1)
185
+ const upload=f.root.querySelector<HTMLInputElement>('input[type=file]')!
186
+ expect(upload.multiple).toBe(false)
187
+ expect(f.root.querySelector(`label[for="${upload.id}"]`)?.textContent).toContain(copy.uploadFile)
188
+ }finally{f.close()}
189
+ })
190
+ test('an oversize file is refused with the limit message and is never uploaded',async()=>{
191
+ const f=fixture(),copy=m2Copy('en')
192
+ try{
193
+ await openFiles(f)
194
+ const upload=f.root.querySelector<HTMLInputElement>('input[type=file]')!
195
+ Object.defineProperty(upload,'files',{value:[new File([new Uint8Array(M2_MAX_BYTES+1)],'big.bin')]})
196
+ upload.dispatchEvent(new Event('change',{bubbles:true}));await settle()
197
+ expect(f.root.textContent).toContain(m2ErrorCopy({code:'limit-exceeded'},copy))
198
+ expect([...f.root.querySelectorAll('button')].find(b=>b.textContent?.trim()===copy.upload)?.disabled).toBe(true)
199
+ expect(f.call.mock.calls.some(([m])=>m==='nas.files.write')).toBe(false)
200
+ }finally{f.close()}
201
+ })
202
+ test('a downloaded read result is a UI Link with the file name',async()=>{
203
+ const f=fixture(),copy=m2Copy('en')
204
+ try{
205
+ await openFiles(f);click(f.root,copy.read);await settle()
206
+ const link=f.root.querySelector<HTMLAnchorElement>('a[download]')!
207
+ expect(link.textContent).toContain(copy.download);expect(link.download).toBe('notes.txt');expect(link.getAttribute('href')).toMatch(/^blob:/)
208
+ }finally{f.close()}
209
+ })
@@ -0,0 +1,27 @@
1
+ import {readdirSync,readFileSync} from 'node:fs'
2
+ import {join} from 'node:path'
3
+ import {expect,test} from 'vitest'
4
+ const root=join(__dirname,'../src')
5
+ const sources=(dir:string):string[]=>readdirSync(dir,{withFileTypes:true}).flatMap(e=>e.isDirectory()?sources(join(dir,e.name)):e.name.endsWith('.vue')?[join(dir,e.name)]:[])
6
+ const template=(file:string)=>readFileSync(file,'utf8').replace(/<script[\s\S]*?<\/script>/g,'').replace(/<style[\s\S]*?<\/style>/g,'')
7
+ const uses=(file:string,tag:string)=>new RegExp(`<${tag}[\\s>/]`).test(template(file))
8
+ // The template ships @synopackageland/ui controls only; a native form control
9
+ // means a missing UI component, which is a UI gap to report, not to paper over.
10
+ test.each(['select','option','textarea','label','input','a'])('no vue template renders a native <%s>',tag=>{
11
+ expect(sources(root).filter(file=>uses(file,tag))).toEqual([])
12
+ })
13
+ // Form direction and the control id come from the UI: `direction="auto"` stacks labels in a
14
+ // narrow package window, and a control inside a FormItem binds its id/ARIA by itself.
15
+ test('FormItem controls rely on the UI auto layout and id binding',()=>{
16
+ const items=sources(root).filter(file=>uses(file,'FormItem'))
17
+ expect(items.length).toBeGreaterThan(0)
18
+ expect(items.filter(file=>/direction="vertical"|field\.controlId|#default="\{field\}"/.test(template(file)))).toEqual([])
19
+ })
20
+ // UI built-in text follows the Provider locale (nine locales), so every app root sets it once and no
21
+ // component repeats a per-control locale or download label.
22
+ test('app roots set the UI locale once and components do not translate UI text themselves',()=>{
23
+ const roots=sources(root).filter(file=>uses(file,'AppRoot'))
24
+ expect(roots.length).toBeGreaterThan(0)
25
+ expect(roots.filter(file=>!uses(file,'ConfigProvider'))).toEqual([])
26
+ expect(sources(root).filter(file=>/download-label|external-label|<FilePicker[^>]*:locale=/.test(template(file)))).toEqual([])
27
+ })
@@ -0,0 +1,110 @@
1
+ // @vitest-environment jsdom
2
+ import {expect,test,vi} from 'vitest'
3
+ import {nextTick} from 'vue'
4
+ import {mount} from '../src/sdk-main'
5
+ import {jobCopy,jobCatalogs} from '../src/jobs/i18n'
6
+ import {m2Copy} from '../src/nas/m2-i18n'
7
+ import type {Context} from '../src/context'
8
+ const id=(c:string)=>c.repeat(64)
9
+ async function settle(){for(let i=0;i<35;i++){await Promise.resolve();await nextTick()}await new Promise(r=>setTimeout(r,25))}
10
+ function click(root:HTMLElement,text:string){const b=[...root.querySelectorAll('button')].find(b=>b.textContent?.trim()===text||b.getAttribute('aria-label')===text);expect(b,text).toBeTruthy();b!.click()}
11
+ function fixture(locale='en'){
12
+ let state='running',reason='',lost=false,user='normal1',deny=false
13
+ const reopen:Array<()=>void>=[]
14
+ const wire=()=>({jobId:id('b'),revision:2,mode:'interactive',state,safeReason:reason||undefined,progress:{completedUnits:state==='succeeded'?1:0,totalUnits:1},drained:state==='succeeded',effectsUnknown:false})
15
+ const call=vi.fn(async(method:string,args?:Record<string,unknown>)=>{
16
+ if(method==='app.status')return {package:'t0090',version:'1',user,admin:false}
17
+ if(method==='integration.status')return {state:'ready',packageRunning:'running',scheduler:'ready'}
18
+ if(method==='personal.list')return {items:[]}
19
+ if(method==='jobs.status')return {revision:2,availability:'session-required',workers:[{id:'inspect',revision:7,actions:['nas.files.list','nas.files.stat','nas.files.read'],maxBytes:4096}],items:call.mock.calls.some(([m])=>m==='jobs.submit')?[wire()]:[]}
20
+ if(method==='nas.discovery'){if(deny)throw {code:'denied',message:'Cookie=CANARY'};return {files:args?.folder?[{name:'fixture.txt',path:'/home/fixture.txt',isdir:false,additional:{size:3}}]:null,shares:args?.folder?undefined:[{name:'Home',path:'/home',isdir:true}],total:1,offset:0}}
21
+ if(method==='nas.grants.confirm')return {grantId:id('a'),folder:args!.folder,writable:args!.writable}
22
+ if(method==='jobs.prepare')return {intentId:id('c'),jobId:id('b'),summaryDigest:id('d'),expiresAt:Date.now()+30000}
23
+ if(method==='jobs.submit'){if(lost)throw {code:'timeout'};return {jobId:id('b'),attachId:id('e'),revision:1,renewAfterMs:2000,ttlMs:6000}}
24
+ if(method==='jobs.lookup')return wire()
25
+ if(method==='jobs.cancel'){state='canceled';return {}}
26
+ throw {code:'unknown_method'}
27
+ })
28
+ const root=document.createElement('div');document.body.append(root)
29
+ const ctx={locale,call,reopen(fn:()=>void){reopen.push(fn);return ()=>{}}} as Context
30
+ const app=mount(root,ctx)
31
+ return {root,app,call,reopen,done(){state='succeeded'},fence(){state='canceled';reason='fenced'},lost(){lost=true},deny(){deny=true},switchUser(){user='normal2'},close(){app.unmount();root.remove()}}
32
+ }
33
+ // The UI Select is a Reka combobox: drive it by keyboard (the UI README helper), which also proves it is keyboard operable.
34
+ // Attrs such as data-job-action land on the focusable combobox itself.
35
+ async function choose(root:HTMLElement,marker:string,label:string){
36
+ const trigger=root.querySelector<HTMLElement>(`[${marker}][role=combobox]`)!;expect(trigger,marker).toBeTruthy()
37
+ expect(trigger.id,marker).toBeTruthy();expect(root.querySelector(`label[for="${trigger.id}"]`),marker).toBeTruthy()
38
+ trigger.dispatchEvent(new KeyboardEvent('keydown',{key:'ArrowDown',bubbles:true,cancelable:true}));await settle()
39
+ const option=[...document.querySelectorAll<HTMLElement>('[role=option]')].find(o=>o.textContent?.trim()===label)
40
+ if(!option||option.getAttribute('aria-disabled')==='true')throw new Error(`No enabled option: ${label}`)
41
+ option.focus();option.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',bubbles:true,cancelable:true}));await settle()
42
+ expect(trigger.textContent).toContain(label)
43
+ }
44
+ async function pick(f:ReturnType<typeof fixture>,file=true){
45
+ const j=jobCopy('en'),n=m2Copy('en')
46
+ click(f.root,j.chooseSource);await settle()
47
+ if(file){click(f.root,'Open Home');await settle();const button=f.root.querySelector<HTMLButtonElement>('button[aria-label="Select fixture.txt"]')!;expect(button.textContent?.trim()).toBe('Select fixture.txt');click(f.root,'Select fixture.txt')}
48
+ else click(f.root,'Select Home')
49
+ await settle();click(f.root,n.confirm);await settle()
50
+ }
51
+ test('SDK mount connects file picker, folder consent, worker revision, single submit and completion/cancel/fence',async()=>{
52
+ const f=fixture(),j=jobCopy('en')
53
+ try{
54
+ await settle();expect(f.call.mock.calls.some(([m])=>m==='personal.list')).toBe(false);await pick(f)
55
+ expect(f.call).toHaveBeenCalledWith('nas.grants.confirm',{folder:'/home',writable:false})
56
+ expect(f.root.textContent).toContain('/home/fixture.txt')
57
+ expect(f.root.textContent).not.toContain('NAS folder grants and file operations are not available yet.')
58
+ click(f.root,j.submitInteractive);click(f.root,j.submitInteractive);await settle()
59
+ expect(f.call).toHaveBeenCalledWith('jobs.prepare',{worker:{id:'inspect',revision:7},parent:{id:id('a'),revision:1},input:{steps:[{action:'nas.files.stat',segments:['fixture.txt']}]}})
60
+ expect(f.call.mock.calls.filter(([m])=>m==='jobs.submit')).toHaveLength(1)
61
+ expect(f.root.querySelector('progress')).toBeTruthy()
62
+ click(f.root,j.cancel);await settle();expect(f.root.textContent).toContain(j.canceled)
63
+ f.fence();click(f.root.querySelector<HTMLElement>('.jobs-panel')!,j.refresh);await settle();expect(f.root.textContent).toContain(j.fenceCanceled)
64
+ f.done();click(f.root.querySelector<HTMLElement>('.jobs-panel')!,j.refresh);await settle();expect(f.root.textContent).toContain(j.succeeded)
65
+ }finally{f.close()}
66
+ })
67
+ test('lost initial submit reply locks the form to original lookup; permissions fail without grant or submit',async()=>{
68
+ const f=fixture(),j=jobCopy('en')
69
+ try{
70
+ await settle();await pick(f);f.lost();click(f.root,j.submitInteractive);await settle()
71
+ expect(f.root.textContent).toContain(j.uncertain)
72
+ const button=[...f.root.querySelectorAll('button')].find(b=>b.textContent?.trim()===j.submitInteractive);expect(button?.disabled).toBe(true)
73
+ click(f.root,j.lookup);await settle()
74
+ expect(f.call).toHaveBeenCalledWith('jobs.lookup',{intentId:id('c')})
75
+ expect(f.call.mock.calls.filter(([m])=>m==='jobs.submit')).toHaveLength(1)
76
+ }finally{f.close()}
77
+ const g=fixture()
78
+ try{await settle();g.deny();click(g.root,j.chooseSource);await settle();expect(g.root.textContent).toContain(m2Copy('en').errorDenied);expect(g.root.textContent).not.toContain('CANARY');expect(g.call.mock.calls.some(([m])=>m==='nas.grants.confirm'||m==='jobs.submit')).toBe(false)}finally{g.close()}
79
+ })
80
+ test('folder list/read bounds and identity switch clear pending input; nine job catalogs stay complete',async()=>{
81
+ const f=fixture(),j=jobCopy('en')
82
+ try{
83
+ await settle();await pick(f,false)
84
+ await choose(f.root,'data-job-action',j.listOperation)
85
+ const limit=f.root.querySelector<HTMLInputElement>('input[data-job-bound]')!;limit.value='101';limit.dispatchEvent(new Event('input',{bubbles:true}));await settle()
86
+ click(f.root,j.submitInteractive);await settle();expect(f.call.mock.calls.some(([m])=>m==='jobs.prepare')).toBe(false)
87
+ limit.value='5';limit.dispatchEvent(new Event('input',{bubbles:true}));await settle();click(f.root,j.submitInteractive);await settle()
88
+ expect(f.call).toHaveBeenCalledWith('jobs.prepare',{worker:{id:'inspect',revision:7},parent:{id:id('a'),revision:1},input:{steps:[{action:'nas.files.list',segments:[],limit:5}]}})
89
+ f.switchUser();f.reopen.forEach(fn=>fn());await settle();expect(f.root.querySelector('[data-job-action]')).toBeNull()
90
+ }finally{f.close()}
91
+ for(const copy of Object.values(jobCatalogs)){expect(Object.keys(copy).sort()).toEqual(Object.keys(jobCatalogs.en).sort());for(const key of Object.keys(copy) as Array<keyof typeof copy>){expect(copy[key].trim()).not.toBe('');expect(copy[key].match(/\{\w+\}/g)??[]).toEqual(jobCatalogs.en[key].match(/\{\w+\}/g)??[])}}
92
+ })
93
+
94
+ test('read input is bounded by the live worker and late prepare cannot submit after account switch',async()=>{
95
+ const f=fixture(),j=jobCopy('en')
96
+ try{
97
+ await settle();await pick(f)
98
+ await choose(f.root,'data-job-action',j.readOperation)
99
+ const bound=f.root.querySelector<HTMLInputElement>('input[data-job-bound]')!;expect(bound.max).toBe('4096');expect(f.root.querySelector<HTMLLabelElement>(`label[for="${bound.id}"]`)?.textContent).toContain(j.byteLimit)
100
+ bound.value='24';bound.dispatchEvent(new Event('input',{bubbles:true}));await settle();click(f.root.querySelector<HTMLElement>('.jobs-panel')!,j.refresh);await settle();expect(bound.value).toBe('24')
101
+ bound.value='4097';bound.dispatchEvent(new Event('input',{bubbles:true}));await settle();click(f.root,j.submitInteractive);await settle();expect(f.call.mock.calls.some(([m])=>m==='jobs.prepare')).toBe(false)
102
+ bound.value='3';bound.dispatchEvent(new Event('input',{bubbles:true}));await settle()
103
+ let done!:(value:unknown)=>void
104
+ const original=f.call.getMockImplementation()!
105
+ f.call.mockImplementation(async(method,args)=>method==='jobs.prepare'?new Promise(resolve=>done=resolve):original(method,args))
106
+ click(f.root,j.submitInteractive);await settle();f.switchUser();f.reopen.forEach(fn=>fn());await settle()
107
+ done({intentId:id('c'),jobId:id('b'),summaryDigest:id('d'),expiresAt:Date.now()+30000});await settle()
108
+ expect(f.call.mock.calls.some(([m])=>m==='jobs.submit')).toBe(false)
109
+ }finally{f.close()}
110
+ })
@@ -0,0 +1,19 @@
1
+ <!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Shell local fixture</title><style>html,body{margin:0;height:100%}#root{height:100%}</style></head>
2
+ <body><div id="root"></div><script type="module">
3
+ import {mount} from '../src/main.ts'
4
+ import {createMockNasService} from '../src/nas/mock-provider.ts'
5
+ // Synthetic responses for local browser QA of the DSM shell. No NAS, no credentials.
6
+ const params=new URLSearchParams(location.search),state=params.get('state')||'ready',locale=params.get('locale')||'en'
7
+ mount(document.querySelector('#root'),{
8
+ locale,reopen(){return()=>{}},
9
+ async call(method){
10
+ if(method==='app.status')return {package:'T0133 Sidebar Demo',version:'0.1.0-0001',user:'admin',admin:true}
11
+ if(method==='integration.status')return {state,reason:state==='ready'?'fixture':'not-requested',packageRunning:'running',scheduler:'ready'}
12
+ if(method==='identity.status')return {mode:'better-auth'}
13
+ if(method==='personal.list')return {items:[{id:'demo-private',name:'My sample note'}]}
14
+ if(method==='personal.read')return {id:'demo-private',text:'Remember to check the DSM window layout.'}
15
+ throw {code:'unknown_method'}
16
+ },
17
+ root:{state:async()=> 'missing',authorize:async()=> 'ready',run:async()=>{},remove:async()=> 'missing'},
18
+ },{nasProvider:createMockNasService().connect('admin')})
19
+ </script></body></html>
@@ -0,0 +1,73 @@
1
+ // @vitest-environment jsdom
2
+ import {expect,test,vi} from 'vitest'
3
+ import {createApp,h,nextTick} from 'vue'
4
+ import SDKApp from '../src/SDKApp.vue'
5
+ import {JobsProviderKey,type JobsProvider} from '../src/jobs/provider'
6
+ import {jobCopy} from '../src/jobs/i18n'
7
+ import {createMockNasService} from '../src/nas/mock-provider'
8
+ import {NasProviderKey} from '../src/nas/provider'
9
+ import {shellCopy} from '../src/shell/i18n'
10
+ import type {Context} from '../src/context'
11
+
12
+ async function settle(){for(let i=0;i<16;i++){await Promise.resolve();await nextTick()}await new Promise(resolve=>setTimeout(resolve,25))}
13
+ function navLabels(root:HTMLElement){return [...root.querySelectorAll<HTMLElement>('[data-nav-item]')].map(node=>node.textContent?.trim())}
14
+ function heading(root:HTMLElement){return root.querySelector('h1')?.textContent?.trim()}
15
+ function pageText(root:HTMLElement,page:string){return root.querySelector(`[data-page="${page}"]`)?.textContent??''}
16
+ function clickNav(root:HTMLElement,label:string){const node=[...root.querySelectorAll<HTMLElement>('[data-nav-item]')].find(node=>node.textContent?.trim()===label);expect(node,label).toBeTruthy();node!.click()}
17
+
18
+ // A connected provider advertises interactive workers; the mock used by the
19
+ // standalone preview does not. This stub keeps both modes on one provider so
20
+ // the shell test can exercise both pages.
21
+ function provider():JobsProvider{
22
+ return {
23
+ mode:'mock',
24
+ readiness:{interactive:'ready',background:'ready'},
25
+ interactive:{workers:[],prepareInteractive:async()=>{throw {code:'unavailable'}},submitInteractive:async()=>{throw {code:'unavailable'}}},
26
+ list:async()=>({revision:1,items:[]}),
27
+ prepare:async()=>{throw {code:'unavailable'}},
28
+ confirm:async()=>{},
29
+ lookup:async()=>undefined,
30
+ cancel:async()=>{},
31
+ revoke:async()=>({revoked:true,drained:true}),
32
+ disconnect(){},
33
+ }
34
+ }
35
+ function fixture(admin:boolean,locale='en'){
36
+ const call=vi.fn(async(method:string)=>{
37
+ if(method==='app.status') return {package:'SdkShell',version:'0.1.0-0001',user:admin?'admin':'normal1',admin}
38
+ if(method==='integration.status') return {state:'ready',reason:'fixture',packageRunning:'unknown',scheduler:'not-probed'}
39
+ if(method==='identity.status') return {mode:'none'}
40
+ throw {code:'unknown_method'}
41
+ })
42
+ const ctx={locale,call,reopen:()=>()=>{}} as unknown as Context
43
+ const root=document.createElement('div');document.body.append(root)
44
+ const app=createApp({render:()=>h(SDKApp,{ctx})})
45
+ app.config.idPrefix=`sdk-shell-${Math.random()}`
46
+ app.provide(JobsProviderKey,provider())
47
+ app.provide(NasProviderKey,createMockNasService().connect(admin?'admin':'normal1'))
48
+ app.mount(root)
49
+ return {root,close(){app.unmount();root.remove()}}
50
+ }
51
+
52
+ test('interactive and background work get separate pages and the admin page is administrator-only',async()=>{
53
+ const admin=fixture(true),other=fixture(false)
54
+ try{
55
+ await settle()
56
+ expect(navLabels(admin.root)).toEqual(['Status','NAS folder access','Interactive jobs','Background jobs','Job management'])
57
+ expect(navLabels(other.root)).toEqual(['Status','NAS folder access','Interactive jobs','Background jobs'])
58
+ expect(heading(admin.root)).toBe(shellCopy('en').status)
59
+
60
+ clickNav(admin.root,shellCopy('en').interactive);await settle()
61
+ expect(heading(admin.root)).toBe('Interactive jobs')
62
+ expect(pageText(admin.root,'interactive')).toContain(jobCopy('en').interactiveHelp)
63
+ expect(pageText(admin.root,'interactive')).not.toContain(jobCopy('en').backgroundHelp)
64
+
65
+ clickNav(admin.root,shellCopy('en').background);await settle()
66
+ expect(heading(admin.root)).toBe('Background jobs')
67
+ expect(pageText(admin.root,'background')).toContain(jobCopy('en').backgroundHelp)
68
+ expect(pageText(admin.root,'background')).not.toContain(jobCopy('en').interactiveHelp)
69
+
70
+ clickNav(other.root,shellCopy('en').interactive);await settle()
71
+ expect(other.root.textContent).not.toContain(jobCopy('en').adminTitle)
72
+ }finally{admin.close();other.close()}
73
+ })
@@ -0,0 +1,114 @@
1
+ // @vitest-environment jsdom
2
+ import {expect,test,vi} from 'vitest'
3
+ import {nextTick} from 'vue'
4
+ import {mount} from '../src/main'
5
+ import {createMockNasService} from '../src/nas/mock-provider'
6
+ import {shellCopy} from '../src/shell/i18n'
7
+ import type {Context,IntegrationState} from '../src/context'
8
+
9
+ async function settle(){for(let i=0;i<16;i++){await Promise.resolve();await nextTick()}await new Promise(resolve=>setTimeout(resolve,25))}
10
+ function nav(root:HTMLElement){return [...root.querySelectorAll<HTMLElement>('[data-nav-item]')]}
11
+ function navLabels(root:HTMLElement){return nav(root).map(node=>node.textContent?.trim())}
12
+ function clickNav(root:HTMLElement,label:string){const node=nav(root).find(node=>node.textContent?.trim()===label);expect(node,label).toBeTruthy();node!.click()}
13
+ function heading(root:HTMLElement){return root.querySelector('h1')?.textContent?.trim()}
14
+ function pageShown(root:HTMLElement,page:string){return root.querySelector<HTMLElement>(`[data-page="${page}"]`)?.style.display !== 'none'}
15
+ // A --background project adds one more page; the same test runs in both builds.
16
+ function withBackground(root:HTMLElement,labels:string[]){return labels.flatMap(label=>label==='@@background@@'?(root.querySelector('[data-background-panel]')?[shellCopy('en').background]:[]):[label])}
17
+
18
+ function fixture(options:{state?:IntegrationState;admin?:boolean;identity?:boolean;locale?:string}={}){
19
+ const flags={state:options.state??'ready' as IntegrationState,identity:options.identity??false,admin:options.admin??true,locale:options.locale??'en'}
20
+ const reopen:Array<()=>void>=[]
21
+ const call=vi.fn(async(method:string)=>{
22
+ if(method==='app.status') return {package:'ShellDemo',version:'0.1.0-0001',user:flags.admin?'admin':'normal1',admin:flags.admin}
23
+ if(method==='integration.status') return {state:flags.state,reason:flags.state==='ready'?'fixture':'not-requested',packageRunning:'unknown',scheduler:'not-probed'}
24
+ if(method==='identity.status') return {mode:flags.identity?'better-auth':'none'}
25
+ if(method==='personal.list') return {items:[{id:'ShellDemo-private',name:'My sample note'}]}
26
+ if(method==='personal.read') return {id:'ShellDemo-private',text:'Private ShellDemo'}
27
+ throw {code:'unknown_method'}
28
+ })
29
+ const ctx={locale:flags.locale,reopen:(fn:()=>void)=>reopen.push(fn),call,root:{state:vi.fn(async()=> 'missing'),authorize:vi.fn(async()=> 'ready'),run:vi.fn(async()=>{}),remove:vi.fn(async()=> 'missing')}} as Context
30
+ const root=document.createElement('div');document.body.append(root)
31
+ const app=mount(root,ctx,{nasProvider:createMockNasService().connect(flags.admin?'admin':'normal1')})
32
+ const refresh=async()=>{reopen.forEach(fn=>fn());await settle()}
33
+ return {root,app,flags,refresh,cleanup(){app.unmount();root.remove()}}
34
+ }
35
+
36
+ test('the sidebar lists the enabled pages and the first one is shown by default',async()=>{
37
+ const f=fixture({identity:true})
38
+ try{
39
+ await settle()
40
+ expect(navLabels(f.root)).toEqual(withBackground(f.root,['Status','My sample data','NAS folder access','@@background@@','Optional app identity']))
41
+ expect(heading(f.root)).toBe('Status')
42
+ expect(nav(f.root).find(node=>node.textContent?.trim()==='Status')?.getAttribute('aria-current')).toBe('page')
43
+ expect(pageShown(f.root,'status')).toBe(true)
44
+ }finally{f.cleanup()}
45
+ })
46
+
47
+ test('choosing a sidebar entry swaps the page and its header',async()=>{
48
+ const f=fixture({identity:true})
49
+ try{
50
+ await settle()
51
+ clickNav(f.root,'NAS folder access');await settle()
52
+ expect(heading(f.root)).toBe('NAS folder access')
53
+ expect(nav(f.root).find(node=>node.textContent?.trim()==='NAS folder access')?.getAttribute('aria-current')).toBe('page')
54
+ expect(nav(f.root).find(node=>node.textContent?.trim()==='Status')?.getAttribute('aria-current')).toBeNull()
55
+ expect(pageShown(f.root,'status')).toBe(false)
56
+ expect(pageShown(f.root,'nas')).toBe(true)
57
+ clickNav(f.root,'My sample data');await settle()
58
+ expect(heading(f.root)).toBe('My sample data')
59
+ }finally{f.cleanup()}
60
+ })
61
+
62
+ test('pages only appear when the manifest enables their feature',async()=>{
63
+ const pending=fixture({state:'pending',identity:false})
64
+ const ready=fixture({state:'ready',identity:false})
65
+ try{
66
+ await settle()
67
+ // Sample data needs a ready integration; identity needs the better-auth build.
68
+ expect(navLabels(pending.root)).toEqual(withBackground(pending.root,['Status','NAS folder access','@@background@@']))
69
+ expect(navLabels(ready.root)).toEqual(withBackground(ready.root,['Status','My sample data','NAS folder access','@@background@@']))
70
+ expect(pending.root.textContent).not.toContain('My sample data')
71
+ }finally{pending.cleanup();ready.cleanup()}
72
+ })
73
+
74
+ test('navigation is keyboard operable through native buttons and the catalog follows DSM locales',async()=>{
75
+ const f=fixture({identity:true,locale:'cht'})
76
+ try{
77
+ await settle()
78
+ for(const node of nav(f.root)){
79
+ expect(node.tagName).toBe('BUTTON')
80
+ expect(node.getAttribute('type')).toBe('button')
81
+ expect(node.getAttribute('tabindex')).toBeNull()
82
+ expect(node.getAttribute('aria-label')||node.textContent?.trim()).toBeTruthy()
83
+ }
84
+ clickNav(f.root,shellCopy('zh-TW').nas);await settle()
85
+ expect(heading(f.root)).toBe(shellCopy('zh-TW').nas)
86
+ }finally{f.cleanup()}
87
+ })
88
+
89
+ test('a page that disappears from the manifest falls back to the first page',async()=>{
90
+ const f=fixture()
91
+ try{
92
+ await settle()
93
+ clickNav(f.root,'My sample data');await settle()
94
+ expect(heading(f.root)).toBe('My sample data')
95
+ // The integration stops being ready while its sample page is selected.
96
+ f.flags.state='pending'
97
+ await f.refresh()
98
+ expect(navLabels(f.root)).toEqual(withBackground(f.root,['Status','NAS folder access','@@background@@']))
99
+ expect(heading(f.root)).toBe('Status')
100
+ expect(pageShown(f.root,'status')).toBe(true)
101
+ expect(pageShown(f.root,'sample')).toBe(false)
102
+ }finally{f.cleanup()}
103
+ })
104
+
105
+ test('the page header and the page content share one PageContent boundary',async()=>{
106
+ const f=fixture({identity:true})
107
+ try{
108
+ await settle()
109
+ const body=f.root.querySelector('[data-page-content-body]')
110
+ expect(body).toBeTruthy()
111
+ expect(body!.contains(f.root.querySelector('h1'))).toBe(true)
112
+ expect(body!.contains(f.root.querySelector('[data-page="status"]'))).toBe(true)
113
+ }finally{f.cleanup()}
114
+ })
@@ -0,0 +1,45 @@
1
+ // @vitest-environment jsdom
2
+ import {expect,test} from 'vitest'
3
+ import {activePage,shellPages,type ShellFeatureFlags} from '../src/shell/pages'
4
+ import {shellCatalogs,shellCopy} from '../src/shell/i18n'
5
+
6
+ test('status is always first and secondary pages follow their feature flag',()=>{
7
+ expect(shellPages({})).toEqual(['status'])
8
+ expect(shellPages({sample:true,nas:true,interactive:true,background:true,admin:true,identity:true}))
9
+ .toEqual(['status','sample','nas','interactive','background','admin','identity'])
10
+ // Unknown or disabled features never leak into the navigation.
11
+ expect(shellPages({sample:false,nas:false,admin:false})).toEqual(['status'])
12
+ })
13
+
14
+ test('a selected page that is no longer available falls back to the first page',()=>{
15
+ const pages=shellPages({sample:true,admin:true})
16
+ expect(activePage('admin',pages)).toBe('admin')
17
+ expect(activePage('nas',pages)).toBe('status')
18
+ expect(activePage(undefined,pages)).toBe('status')
19
+ expect(activePage('status',shellPages({folders:true}))).toBe('status')
20
+ expect(shellPages({folders:true})).toEqual(['status','folders'])
21
+ })
22
+
23
+ test('feature flags accept plain booleans without a build-time manifest read',()=>{
24
+ const flags:ShellFeatureFlags={sample:true,interactive:false}
25
+ expect(shellPages(flags)).toEqual(['status','sample'])
26
+ })
27
+
28
+ test('nine navigation catalogs stay complete and resolve DSM locale aliases',()=>{
29
+ expect(Object.keys(shellCatalogs)).toEqual(['en','zh-TW','zh-CN','ja','ko','de','fr','es','it'])
30
+ for(const catalog of Object.values(shellCatalogs)){
31
+ expect(Object.keys(catalog).sort()).toEqual(Object.keys(shellCatalogs.en).sort())
32
+ expect(Object.values(catalog).every(value=>typeof value==='string'&&value.trim().length>0)).toBe(true)
33
+ }
34
+ expect(shellCopy('cht')).toEqual(shellCatalogs['zh-TW'])
35
+ expect(shellCopy('zh_CN')).toEqual(shellCatalogs['zh-CN'])
36
+ expect(shellCopy('unknown')).toEqual(shellCatalogs.en)
37
+ expect(Object.values(shellCatalogs).every(catalog=>typeof catalog.background==='string'&&catalog.background.trim().length>0)).toBe(true)
38
+ })
39
+
40
+ test('navigation labels are unique inside every catalog',()=>{
41
+ for(const catalog of Object.values(shellCatalogs)){
42
+ const labels=Object.values(catalog).map(value=>value.trim().toLowerCase())
43
+ expect(new Set(labels).size).toBe(labels.length)
44
+ }
45
+ })
@@ -1,4 +1,4 @@
1
- import { defineConfig } from 'vite'
1
+ import { defineConfig, loadEnv } from 'vite'
2
2
  import vue from '@vitejs/plugin-vue'
3
3
  // @ts-expect-error The shared notice tool is a plain Node module.
4
4
  import { writeThirdPartyNotices } from './third-party-notices.mjs'
@@ -6,5 +6,5 @@ export default defineConfig(({command}) => ({
6
6
  plugins: [vue(), {name: 'dependency-notices', closeBundle() {writeThirdPartyNotices('.', 'dist/LICENSES', {ui: true})}}],
7
7
  // Library mode must replace Vue dependency env guards for the DSM browser.
8
8
  define: command === 'build' ? {'process.env.NODE_ENV': JSON.stringify('production')} : {},
9
- build: {lib: {entry: 'src/main.ts', formats: ['iife'], name: 'DsmVueApp', fileName: () => 'app.js'}, cssCodeSplit: false, rollupOptions: {output: {assetFileNames: 'app.[ext]'}}},
9
+ build: {lib: {entry: loadEnv('production', '.', '').SYNOPKGLAND_M2_SIMPLE === '1' ? 'src/m2-main.ts' : loadEnv('production', '.', '').SYNOPKGLAND_SDK_WORKERS === '1' ? 'src/sdk-main.ts' : 'src/main.ts', formats: ['iife'], name: 'DsmVueApp', fileName: () => 'app.js'}, cssCodeSplit: false, rollupOptions: {output: {assetFileNames: 'app.[ext]'}}},
10
10
  }))