@synopackageland/cli 0.3.9 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (286) hide show
  1. package/README.md +20 -9
  2. package/dist/bin.js +1 -2
  3. package/dist/build-spk/generator.d.ts +0 -3
  4. package/dist/build-spk/generator.js +135 -287
  5. package/dist/build-spk/native-lifecycle.js +0 -3
  6. package/dist/build-spk/native-payload.js +0 -1
  7. package/dist/cli.js +171 -151
  8. package/dist/compose/analyze.d.ts +1 -2
  9. package/dist/compose/analyze.js +5 -48
  10. package/dist/contract/unsupported-types.d.ts +10 -0
  11. package/dist/contract/unsupported-types.js +49 -0
  12. package/dist/contract/validate.js +25 -2
  13. package/dist/contract/web-launch.js +0 -2
  14. package/dist/deploy/deploy.d.ts +1 -4
  15. package/dist/deploy/deploy.js +57 -156
  16. package/dist/deploy/homes-values.js +1 -1
  17. package/dist/deploy/plan.d.ts +18 -0
  18. package/dist/deploy/plan.js +54 -0
  19. package/dist/deploy/port-preflight.d.ts +21 -0
  20. package/dist/deploy/port-preflight.js +42 -0
  21. package/dist/deploy/spk-version.d.ts +2 -14
  22. package/dist/deploy/spk-version.js +25 -111
  23. package/dist/dev.d.ts +1 -3
  24. package/dist/dev.js +14 -28
  25. package/dist/error-registry.js +810 -857
  26. package/dist/host/dsm-client.js +23 -263
  27. package/dist/host/dsm-port-profile.d.ts +47 -0
  28. package/dist/host/dsm-port-profile.js +206 -0
  29. package/dist/host/port-info-lookup.d.ts +9 -0
  30. package/dist/host/port-info-lookup.js +87 -0
  31. package/dist/host/port-info.d.ts +33 -0
  32. package/dist/host/port-info.js +66 -0
  33. package/dist/host/types.d.ts +6 -78
  34. package/dist/index.d.ts +0 -9
  35. package/dist/index.js +0 -6
  36. package/dist/info.d.ts +0 -1
  37. package/dist/info.js +3 -10
  38. package/dist/init.d.ts +11 -1
  39. package/dist/init.js +112 -4
  40. package/dist/log.js +2 -3
  41. package/dist/native/validate.js +0 -1
  42. package/dist/standalone/binary.d.ts +9 -0
  43. package/dist/standalone/binary.js +67 -0
  44. package/dist/standalone/build.d.ts +25 -0
  45. package/dist/standalone/build.js +108 -0
  46. package/dist/standalone/dsm-window.d.ts +26 -0
  47. package/dist/standalone/dsm-window.js +251 -0
  48. package/dist/standalone/helper.d.ts +31 -0
  49. package/dist/standalone/helper.js +104 -0
  50. package/dist/standalone/i18n.d.ts +28 -0
  51. package/dist/standalone/i18n.js +161 -0
  52. package/dist/standalone/inspect-spk.d.ts +21 -0
  53. package/dist/standalone/inspect-spk.js +160 -0
  54. package/dist/standalone/manifest.d.ts +71 -0
  55. package/dist/standalone/manifest.js +276 -0
  56. package/dist/standalone/root-messages.d.ts +7 -0
  57. package/dist/standalone/root-messages.js +250 -0
  58. package/dist/standalone/root-ops.d.ts +42 -0
  59. package/dist/standalone/root-ops.js +101 -0
  60. package/dist/standalone/root-window.d.ts +35 -0
  61. package/dist/standalone/root-window.js +290 -0
  62. package/dist/standalone/spk.d.ts +22 -0
  63. package/dist/standalone/spk.js +205 -0
  64. package/dist/templates/binary/synology-app.yaml +7 -0
  65. package/dist/templates/hello-files/compose.yaml +0 -1
  66. package/dist/templates/hello-web/compose.yaml +13 -10
  67. package/dist/templates/hello-web/html/app.js +1 -23
  68. package/dist/templates/hello-web/html/index.html +1 -3
  69. package/dist/templates/hello-web-page/compose.yaml +13 -10
  70. package/dist/templates/hello-web-page/html/app.js +1 -23
  71. package/dist/templates/hello-web-page/html/index.html +1 -3
  72. package/dist/templates/native-go/README.md +268 -0
  73. package/dist/templates/native-go/backend/cmd/app/main.go +49 -0
  74. package/dist/templates/native-go/backend/cmd/helper/main.go +38 -0
  75. package/dist/templates/native-go/backend/go.mod +3 -0
  76. package/dist/templates/native-go/backend/internal/api/errors.go +33 -0
  77. package/dist/templates/native-go/backend/internal/api/registry.go +94 -0
  78. package/dist/templates/native-go/backend/internal/api/registry_test.go +329 -0
  79. package/dist/templates/native-go/backend/internal/api/server.go +155 -0
  80. package/dist/templates/native-go/backend/internal/app/root_none.go +9 -0
  81. package/dist/templates/native-go/backend/internal/app/routes.go +127 -0
  82. package/dist/templates/native-go/backend/internal/app/routes_test.go +167 -0
  83. package/dist/templates/native-go/backend/internal/dsm/auth.go +118 -0
  84. package/dist/templates/native-go/backend/internal/dsm/auth_test.go +139 -0
  85. package/dist/templates/native-go/backend/internal/helper/control.go +219 -0
  86. package/dist/templates/native-go/backend/internal/helper/control_test.go +242 -0
  87. package/dist/templates/native-go/backend/internal/helper/peer_linux.go +44 -0
  88. package/dist/templates/native-go/backend/internal/helper/peer_other.go +7 -0
  89. package/dist/templates/native-go/backend/internal/helper/process_linux.go +167 -0
  90. package/dist/templates/native-go/backend/internal/helper/process_other.go +5 -0
  91. package/dist/templates/native-go/backend/internal/helper/server_linux.go +83 -0
  92. package/dist/templates/native-go/backend/internal/helper/status.go +271 -0
  93. package/dist/templates/native-go/backend/internal/helper/status_test.go +205 -0
  94. package/dist/templates/native-go/backend/internal/helper/supervision.go +84 -0
  95. package/dist/templates/native-go/backend/internal/helper/supervision_test.go +124 -0
  96. package/dist/templates/native-go/backend/internal/helper/supervisor_linux.go +192 -0
  97. package/dist/templates/native-go/backend/internal/helper/supervisor_linux_test.go +302 -0
  98. package/dist/templates/native-go/dsm-package.yaml +58 -0
  99. package/dist/templates/native-go/gitignore +1 -0
  100. package/dist/templates/native-go/i18n/glossary.json +662 -0
  101. package/dist/templates/native-go/i18n/messages/de.json +28 -0
  102. package/dist/templates/native-go/i18n/messages/en.json +28 -0
  103. package/dist/templates/native-go/i18n/messages/es.json +28 -0
  104. package/dist/templates/native-go/i18n/messages/fr.json +28 -0
  105. package/dist/templates/native-go/i18n/messages/it.json +28 -0
  106. package/dist/templates/native-go/i18n/messages/ja.json +28 -0
  107. package/dist/templates/native-go/i18n/messages/ko.json +28 -0
  108. package/dist/templates/native-go/i18n/messages/zh-CN.json +28 -0
  109. package/dist/templates/native-go/i18n/messages/zh-TW.json +28 -0
  110. package/dist/templates/native-go/ui/app.css +35 -0
  111. package/dist/templates/native-go/ui/app.js +94 -0
  112. package/dist/templates/native-go-better-auth/backend/internal/app/identity.go +208 -0
  113. package/dist/templates/native-go-better-auth/backend/internal/app/identity_test.go +113 -0
  114. package/dist/templates/native-go-better-auth/node/app.mjs +72 -0
  115. package/dist/templates/native-go-better-auth/node/build.mjs +2 -0
  116. package/dist/templates/native-go-better-auth/node/package-lock.json +862 -0
  117. package/dist/templates/native-go-better-auth/node/package.json +1 -0
  118. package/dist/templates/native-go-better-auth/node/test/auth.test.mjs +31 -0
  119. package/dist/templates/native-go-integration/backend/internal/app/identity_none.go +6 -0
  120. package/dist/templates/native-go-integration/backend/internal/app/personal.go +203 -0
  121. package/dist/templates/native-go-integration/backend/internal/app/personal_test.go +119 -0
  122. package/dist/templates/native-go-root/backend/internal/app/root.go +107 -0
  123. package/dist/templates/native-go-root/backend/internal/app/root_test.go +124 -0
  124. package/dist/templates/native-go-root/root/whoami.sh +6 -0
  125. package/dist/templates/native-go-vue/README.md +110 -0
  126. package/dist/templates/native-go-vue/frontend/DESIGN.md +78 -0
  127. package/dist/templates/native-go-vue/frontend/package-lock.json +2507 -0
  128. package/dist/templates/native-go-vue/frontend/package.json +6 -0
  129. package/dist/templates/native-go-vue/frontend/src/App.vue +132 -0
  130. package/dist/templates/native-go-vue/frontend/src/context.ts +17 -0
  131. package/dist/templates/native-go-vue/frontend/src/main.ts +26 -0
  132. package/dist/templates/native-go-vue/frontend/src/style.css +14 -0
  133. package/dist/templates/native-go-vue/frontend/test/apps.test.ts +148 -0
  134. package/dist/templates/native-go-vue/frontend/tsconfig.json +1 -0
  135. package/dist/templates/native-go-vue/frontend/vite.config.ts +8 -0
  136. package/dist/templates/native-go-vue/gitignore +2 -0
  137. package/dist/types.d.ts +1 -1
  138. package/dist/validate.js +11 -7
  139. package/package.json +3 -7
  140. package/schema/synology-app.schema.json +36 -37
  141. package/skills/SKILL.md +4 -48
  142. package/skills/reference/create.md +11 -83
  143. package/skills/reference/deploy.md +7 -78
  144. package/skills/reference/diagnose.md +4 -101
  145. package/skills/reference/files.md +5 -41
  146. package/skills/reference/identity.md +3 -29
  147. package/skills/reference/wrap.md +0 -2
  148. package/templates/binary/synology-app.yaml +7 -0
  149. package/templates/hello-files/compose.yaml +0 -1
  150. package/templates/hello-web/compose.yaml +13 -10
  151. package/templates/hello-web/html/app.js +1 -23
  152. package/templates/hello-web/html/index.html +1 -3
  153. package/templates/hello-web-page/compose.yaml +13 -10
  154. package/templates/hello-web-page/html/app.js +1 -23
  155. package/templates/hello-web-page/html/index.html +1 -3
  156. package/templates/native-go/README.md +268 -0
  157. package/templates/native-go/backend/cmd/app/main.go +49 -0
  158. package/templates/native-go/backend/cmd/helper/main.go +38 -0
  159. package/templates/native-go/backend/go.mod +3 -0
  160. package/templates/native-go/backend/internal/api/errors.go +33 -0
  161. package/templates/native-go/backend/internal/api/registry.go +94 -0
  162. package/templates/native-go/backend/internal/api/registry_test.go +329 -0
  163. package/templates/native-go/backend/internal/api/server.go +155 -0
  164. package/templates/native-go/backend/internal/app/root_none.go +9 -0
  165. package/templates/native-go/backend/internal/app/routes.go +127 -0
  166. package/templates/native-go/backend/internal/app/routes_test.go +167 -0
  167. package/templates/native-go/backend/internal/dsm/auth.go +118 -0
  168. package/templates/native-go/backend/internal/dsm/auth_test.go +139 -0
  169. package/templates/native-go/backend/internal/helper/control.go +219 -0
  170. package/templates/native-go/backend/internal/helper/control_test.go +242 -0
  171. package/templates/native-go/backend/internal/helper/peer_linux.go +44 -0
  172. package/templates/native-go/backend/internal/helper/peer_other.go +7 -0
  173. package/templates/native-go/backend/internal/helper/process_linux.go +167 -0
  174. package/templates/native-go/backend/internal/helper/process_other.go +5 -0
  175. package/templates/native-go/backend/internal/helper/server_linux.go +83 -0
  176. package/templates/native-go/backend/internal/helper/status.go +271 -0
  177. package/templates/native-go/backend/internal/helper/status_test.go +205 -0
  178. package/templates/native-go/backend/internal/helper/supervision.go +84 -0
  179. package/templates/native-go/backend/internal/helper/supervision_test.go +124 -0
  180. package/templates/native-go/backend/internal/helper/supervisor_linux.go +192 -0
  181. package/templates/native-go/backend/internal/helper/supervisor_linux_test.go +302 -0
  182. package/templates/native-go/dsm-package.yaml +58 -0
  183. package/templates/native-go/gitignore +1 -0
  184. package/templates/native-go/i18n/glossary.json +662 -0
  185. package/templates/native-go/i18n/messages/de.json +28 -0
  186. package/templates/native-go/i18n/messages/en.json +28 -0
  187. package/templates/native-go/i18n/messages/es.json +28 -0
  188. package/templates/native-go/i18n/messages/fr.json +28 -0
  189. package/templates/native-go/i18n/messages/it.json +28 -0
  190. package/templates/native-go/i18n/messages/ja.json +28 -0
  191. package/templates/native-go/i18n/messages/ko.json +28 -0
  192. package/templates/native-go/i18n/messages/zh-CN.json +28 -0
  193. package/templates/native-go/i18n/messages/zh-TW.json +28 -0
  194. package/templates/native-go/ui/app.css +35 -0
  195. package/templates/native-go/ui/app.js +94 -0
  196. package/templates/native-go-better-auth/backend/internal/app/identity.go +208 -0
  197. package/templates/native-go-better-auth/backend/internal/app/identity_test.go +113 -0
  198. package/templates/native-go-better-auth/node/app.mjs +72 -0
  199. package/templates/native-go-better-auth/node/build.mjs +2 -0
  200. package/templates/native-go-better-auth/node/package-lock.json +862 -0
  201. package/templates/native-go-better-auth/node/package.json +1 -0
  202. package/templates/native-go-better-auth/node/test/auth.test.mjs +31 -0
  203. package/templates/native-go-integration/backend/internal/app/identity_none.go +6 -0
  204. package/templates/native-go-integration/backend/internal/app/personal.go +203 -0
  205. package/templates/native-go-integration/backend/internal/app/personal_test.go +119 -0
  206. package/templates/native-go-root/backend/internal/app/root.go +107 -0
  207. package/templates/native-go-root/backend/internal/app/root_test.go +124 -0
  208. package/templates/native-go-root/root/whoami.sh +6 -0
  209. package/templates/native-go-vue/README.md +110 -0
  210. package/templates/native-go-vue/frontend/DESIGN.md +78 -0
  211. package/templates/native-go-vue/frontend/package-lock.json +2507 -0
  212. package/templates/native-go-vue/frontend/package.json +6 -0
  213. package/templates/native-go-vue/frontend/src/App.vue +132 -0
  214. package/templates/native-go-vue/frontend/src/context.ts +17 -0
  215. package/templates/native-go-vue/frontend/src/main.ts +26 -0
  216. package/templates/native-go-vue/frontend/src/style.css +14 -0
  217. package/templates/native-go-vue/frontend/test/apps.test.ts +148 -0
  218. package/templates/native-go-vue/frontend/tsconfig.json +1 -0
  219. package/templates/native-go-vue/frontend/vite.config.ts +8 -0
  220. package/templates/native-go-vue/gitignore +2 -0
  221. package/dist/attestation/app-package.d.ts +0 -86
  222. package/dist/attestation/app-package.js +0 -206
  223. package/dist/attestation/install-on-host.d.ts +0 -14
  224. package/dist/attestation/install-on-host.js +0 -52
  225. package/dist/attestation/keyring.d.ts +0 -21
  226. package/dist/attestation/keyring.js +0 -168
  227. package/dist/attestation/platform-service.d.ts +0 -70
  228. package/dist/attestation/platform-service.js +0 -250
  229. package/dist/build-spk/broker-glue.d.ts +0 -16
  230. package/dist/build-spk/broker-glue.js +0 -40
  231. package/dist/deploy/catalog-publish.d.ts +0 -3
  232. package/dist/deploy/catalog-publish.js +0 -7
  233. package/dist/doctor.d.ts +0 -22
  234. package/dist/doctor.js +0 -721
  235. package/dist/error-collector.d.ts +0 -2
  236. package/dist/error-collector.js +0 -46
  237. package/dist/host/broker-paths.d.ts +0 -1
  238. package/dist/host/broker-paths.js +0 -1
  239. package/dist/host/broker-upgrade-preflight.d.ts +0 -61
  240. package/dist/host/broker-upgrade-preflight.js +0 -156
  241. package/dist/host/digests.d.ts +0 -5
  242. package/dist/host/digests.js +0 -63
  243. package/dist/host/fake-nas.d.ts +0 -41
  244. package/dist/host/fake-nas.js +0 -174
  245. package/dist/mock-broker/bootstrap.d.ts +0 -2
  246. package/dist/mock-broker/bootstrap.js +0 -8
  247. package/dist/mock-broker/files.d.ts +0 -60
  248. package/dist/mock-broker/files.js +0 -408
  249. package/dist/mock-broker/oidc.d.ts +0 -31
  250. package/dist/mock-broker/oidc.js +0 -161
  251. package/dist/mock-broker/path.d.ts +0 -9
  252. package/dist/mock-broker/path.js +0 -50
  253. package/dist/mock-broker/server.d.ts +0 -19
  254. package/dist/mock-broker/server.js +0 -350
  255. package/dist/publish/build-ledger.d.ts +0 -22
  256. package/dist/publish/build-ledger.js +0 -26
  257. package/dist/publish/community-path.d.ts +0 -6
  258. package/dist/publish/community-path.js +0 -41
  259. package/dist/publish/curated-release.d.ts +0 -78
  260. package/dist/publish/curated-release.js +0 -883
  261. package/dist/publish/file-build-ledger.d.ts +0 -18
  262. package/dist/publish/file-build-ledger.js +0 -268
  263. package/dist/publish/github-pr.d.ts +0 -25
  264. package/dist/publish/github-pr.js +0 -85
  265. package/dist/publish/github-repo.d.ts +0 -8
  266. package/dist/publish/github-repo.js +0 -21
  267. package/dist/publish/platform-service-release.d.ts +0 -60
  268. package/dist/publish/platform-service-release.js +0 -333
  269. package/dist/publish/preflight.d.ts +0 -24
  270. package/dist/publish/preflight.js +0 -138
  271. package/dist/publish/publish.d.ts +0 -27
  272. package/dist/publish/publish.js +0 -136
  273. package/dist/publish/sync-definition.d.ts +0 -4
  274. package/dist/publish/sync-definition.js +0 -48
  275. package/dist/release-bin.d.ts +0 -2
  276. package/dist/release-bin.js +0 -23
  277. package/dist/templates/hello-web/html/sdk.js +0 -68
  278. package/dist/templates/hello-web-page/html/sdk.js +0 -68
  279. package/dist/templates/native/synology-app.yaml +0 -7
  280. package/dist/test-host.d.ts +0 -15
  281. package/dist/test-host.js +0 -37
  282. package/templates/hello-web/html/sdk.js +0 -68
  283. package/templates/hello-web-page/html/sdk.js +0 -68
  284. package/templates/native/synology-app.yaml +0 -7
  285. /package/dist/templates/{native → binary}/native.yaml +0 -0
  286. /package/templates/{native → binary}/native.yaml +0 -0
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "native-dsm-vue-app", "private": true, "type": "module",
3
+ "scripts": {"build": "vue-tsc --noEmit && vite build", "test": "vitest run"},
4
+ "dependencies": {"@synopackageland/ui": "0.4.0", "vue": "3.5.43"},
5
+ "devDependencies": {"@vitejs/plugin-vue": "6.0.9", "vite": "7.3.6", "typescript": "5.9.3", "vue-tsc": "3.3.11", "vitest": "3.2.4", "jsdom": "26.1.0"}
6
+ }
@@ -0,0 +1,132 @@
1
+ <script setup lang="ts">
2
+ import {computed, onMounted, onUnmounted, ref} from 'vue'
3
+ import {AppRoot, Button, Callout, Dialog, SettingsSection, Textarea} from '@synopackageland/ui'
4
+ import {stateCopy, type AppStatus, type Context, type Status} from './context'
5
+ const props = defineProps<{ctx: Context}>()
6
+ const app = ref<AppStatus>()
7
+ const status = ref<Status>()
8
+ const busy = ref(false)
9
+ const error = ref('')
10
+ const notice = ref('')
11
+ const disableOpen = ref(false)
12
+ const id = ref('')
13
+ const text = ref('')
14
+ const identity = ref('')
15
+ let disposed = false
16
+ const refreshing = ref(false)
17
+ let appCookie = ''
18
+ const configured = computed(() => status.value?.reason !== "not-requested")
19
+ const details = computed(() => status.value ? stateCopy[status.value.state] : undefined)
20
+ function message(e: unknown) {
21
+ const code = (e as {code?: string})?.code || 'request_failed'
22
+ return ({session_required:'Your DSM session expired. Sign in again.',access_denied:'This sample belongs to another account.',integration_unavailable:'Integration is unavailable. Ask an administrator to activate or recover it.',personal_unavailable:'Sample storage is unavailable. Ask an administrator to check package data.',root_conflict:'A conflicting scheduler task exists. Ask an administrator to review it.',root_verify_failed:'Scheduler verification failed. Refresh and review the task.',root_run_failed:'The approved operation failed. Review integration status and cleanup.'} as Record<string,string>)[code] || `Request failed (${code}). Refresh and try again.`
23
+ }
24
+ async function refresh() {
25
+ if(disposed || refreshing.value) return
26
+ refreshing.value = true; error.value = ''
27
+ try {
28
+ const [a,s] = await Promise.all([props.ctx.call<AppStatus>('app.status'),props.ctx.call<Status>('integration.status')])
29
+ if(disposed) return
30
+ app.value = a; status.value = s
31
+ if(s.state !== 'ready') {id.value='';text.value='';return}
32
+ // Plain --ui vue has no personal routes; not-enabled status hides this.
33
+ const list = await props.ctx.call<{items:Array<{id:string;name:string}>}>('personal.list')
34
+ if(disposed) return
35
+ id.value = list.items[0]?.id || ''
36
+ if(id.value) {
37
+ const note = await props.ctx.call<{text:string}>('personal.read',{id:id.value})
38
+ if(!disposed) text.value = note.text
39
+ }
40
+ } catch(e) {if(!disposed) {error.value = message(e);status.value=undefined;id.value='';text.value=''}} finally {refreshing.value=false}
41
+ }
42
+ async function action(op: string) {
43
+ if(busy.value || refreshing.value || disposed) return
44
+ busy.value=true;error.value='';notice.value=''
45
+ try {
46
+ if(!props.ctx.root) throw {code:'unsupported'}
47
+ const approved = await props.ctx.root.authorize(op)
48
+ if(disposed) return
49
+ if(approved !== 'ready') {notice.value='Approval cancelled. No operation was run.';return}
50
+ await props.ctx.root.run(op)
51
+ if(disposed) return
52
+ notice.value='Operation completed. Integration status was checked again.'
53
+ await refresh()
54
+ } catch(e) {if(!disposed) {await refresh();if(!disposed) error.value=message(e)}}
55
+ finally {if(!disposed) busy.value=false}
56
+ }
57
+ async function save() {
58
+ if(busy.value || refreshing.value || !id.value || disposed) return
59
+ busy.value=true;error.value='';notice.value=''
60
+ try {await props.ctx.call('personal.write',{id:id.value,text:text.value});if(!disposed) notice.value='Your sample note was saved.'}
61
+ catch(e) {if(!disposed) error.value=message(e)} finally {if(!disposed) busy.value=false}
62
+ }
63
+ async function login() {
64
+ if(busy.value || refreshing.value || disposed) return
65
+ busy.value=true;error.value=''
66
+ try {
67
+ const result=await props.ctx.call<{cookie:string;user:string}>('identity.login')
68
+ if(!disposed){appCookie=result.cookie;identity.value=`App session for ${result.user}`}
69
+ } catch(e) {if(!disposed) error.value=message(e)} finally {if(!disposed) busy.value=false}
70
+ }
71
+ async function checkIdentity() {
72
+ if(busy.value || refreshing.value || disposed) return
73
+ busy.value=true;error.value=''
74
+ try {const result=await props.ctx.call<{user:string}>('identity.current',{cookie:appCookie});if(!disposed) identity.value=`App session for ${result.user}`}
75
+ catch(e) {if(!disposed) {appCookie='';identity.value='';error.value=message(e)}} finally {if(!disposed) busy.value=false}
76
+ }
77
+ const hasIdentity = ref(false)
78
+ onMounted(async()=>{
79
+ props.ctx.reopen(()=>{if(!disposed) void refresh()})
80
+ await refresh()
81
+ if(disposed || !app.value) return
82
+ try {const result = await props.ctx.call<{mode:string}>('identity.status');if(!disposed) hasIdentity.value = result.mode==='better-auth'} catch { /* Optional route absent in Go-only. */ }
83
+ })
84
+ onUnmounted(()=>{disposed=true;appCookie='';text.value='';id.value=''})
85
+ </script>
86
+
87
+ <template>
88
+ <AppRoot modal-scope="app" style="height: 100%">
89
+ <main class="integration-app" :aria-busy="busy || refreshing" aria-label="Package settings">
90
+ <header class="integration-heading">
91
+ <div><h1>{{ app?.package || 'Package settings' }}</h1><p v-if="app">{{ app.user }} · {{ app.version }}</p></div>
92
+ <Button variant="secondary" :disabled="busy || refreshing" @click="error=''; refresh()">Refresh</Button>
93
+ </header>
94
+ <p v-if="refreshing" role="status">Refreshing status…</p>
95
+ <p v-else-if="busy" role="status">Completing operation…</p>
96
+ <Callout v-if="error" tone="danger" title="Action required"><span role="alert">{{ error }}</span></Callout>
97
+ <p v-if="notice" class="integration-notice" role="status">{{ notice }}</p>
98
+ <SettingsSection title="DSM integration" :collapsible="false">
99
+ <div v-if="status && details" :data-integration-state="status.state" class="integration-status" role="status">
100
+ <h2>{{ details.label }}</h2><p>{{ details.description }}</p>
101
+ <p v-if="status.cleanup === 'required'">Cleanup is required. Ask an administrator to run teardown or disable.</p>
102
+ <dl><dt>Package running</dt><dd>{{ status.packageRunning }}</dd><dt>Scheduler</dt><dd>{{ status.scheduler }}</dd></dl>
103
+ </div>
104
+ <p v-else-if="error" role="status">Status could not be loaded. Refresh to try again.</p>
105
+ <p v-else role="status">Loading integration status…</p>
106
+ <div v-if="app?.admin && ctx.root && configured" class="integration-actions">
107
+ <Button variant="primary" :disabled="busy || refreshing" @click="action('helper_enable')">Approve and activate</Button>
108
+ <Button variant="secondary" :disabled="busy || refreshing" @click="action('helper_recover')">Recover</Button>
109
+ <Button variant="secondary" :disabled="busy || refreshing" @click="action('helper_teardown')">Teardown</Button>
110
+ <Button variant="secondary" :disabled="busy || refreshing" @click="disableOpen=true">Disable</Button>
111
+ </div>
112
+ <Callout v-if="app && !app.admin && status?.state !== 'ready'" title="Administrator action needed">Ask a DSM administrator to open this package's settings. Your login cannot approve root operations.</Callout>
113
+ </SettingsSection>
114
+ <SettingsSection v-if="status?.state === 'ready'" title="My sample data" :collapsible="false">
115
+ <p class="integration-help">This synthetic note belongs to your DSM account and is stored in package data.</p>
116
+ <p v-if="refreshing" role="status">Loading your sample data…</p>
117
+ <Callout v-else-if="!id" title="No sample data">Refresh to request your sample data again.</Callout>
118
+ <label v-if="id" class="integration-field"><span>My sample note</span><Textarea v-model="text" aria-label="My sample note" :disabled="busy || refreshing" /></label>
119
+ <Button v-if="id" variant="primary" :disabled="busy || refreshing" @click="save">Save my note</Button>
120
+ </SettingsSection>
121
+ <SettingsSection v-if="hasIdentity" title="Optional app identity" :collapsible="false">
122
+ <p class="integration-help">Your DSM login is checked again for every app session request.</p>
123
+ <div class="integration-actions"><Button variant="primary" :disabled="busy || refreshing" @click="login">Create app session</Button><Button variant="secondary" :disabled="busy || refreshing || !identity" @click="checkIdentity">Check app session</Button></div>
124
+ <p v-if="identity" role="status">{{ identity }}</p>
125
+ </SettingsSection>
126
+ <Dialog v-model:open="disableOpen" title="Disable integration?" description="New integration requests will be refused before helper cleanup begins.">
127
+ <p>Your sample data and historical root output are retained. A failed cleanup remains visible.</p>
128
+ <template #footer><Button variant="secondary" @click="disableOpen=false">Cancel</Button><Button variant="danger" :disabled="busy || refreshing" @click="disableOpen=false; action('helper_disable')">Confirm disable</Button></template>
129
+ </Dialog>
130
+ </main>
131
+ </AppRoot>
132
+ </template>
@@ -0,0 +1,17 @@
1
+ export type IntegrationState = 'not-enabled' | 'ready' | 'needs-approval' | 'incompatible' | 'stopped' | 'failed'
2
+ export interface Status {state: IntegrationState; reason?: string; cleanup?: string; packageRunning: string; scheduler: string}
3
+ export interface Context {
4
+ call<T = unknown>(method: string, params?: Record<string, unknown>): Promise<T>
5
+ reopen(fn: () => void): void
6
+ locale: string
7
+ root?: {state(op: string): Promise<string>; authorize(op: string): Promise<string>; run(op: string): Promise<unknown>; remove(op: string): Promise<string>}
8
+ }
9
+ export interface AppStatus {package: string; version: string; user: string; admin: boolean}
10
+ export const stateCopy: Record<IntegrationState, {label: string; description: string}> = {
11
+ 'not-enabled': {label: 'Not enabled', description: 'An administrator can enable integration for this package.'},
12
+ ready: {label: 'Ready', description: 'The helper passed a fresh trusted health check.'},
13
+ 'needs-approval': {label: 'Approval required', description: 'Ask an administrator to approve and activate integration.'},
14
+ incompatible: {label: 'Incompatible', description: 'Inventory or trust checks failed. Ask an administrator to review this package.'},
15
+ stopped: {label: 'Stopped', description: 'An administrator must recover integration after start or reboot.'},
16
+ failed: {label: 'Failed', description: 'Integration could not finish. Review cleanup status before retrying.'},
17
+ }
@@ -0,0 +1,26 @@
1
+ import {createApp} from 'vue'
2
+ import App from './App.vue'
3
+ import type {Context} from './context'
4
+ import '@synopackageland/ui/themes/dsm7-embedded.css'
5
+ import './style.css'
6
+
7
+ let mountSequence = 0
8
+ export function mount(root: HTMLElement, ctx: Context) {
9
+ const app = createApp(App, {ctx})
10
+ // Include the generated package ID, an instance counter and randomness even
11
+ // across separately loaded bundles. getRandomValues also works on DSM HTTP.
12
+ app.config.idPrefix = `__PACKAGE__-${++mountSequence}-${crypto.getRandomValues(new Uint32Array(4)).join('-')}`
13
+ const hadRootClass = root.classList.contains('dsmapp-vue-root')
14
+ root.classList.add('dsmapp-vue-root')
15
+ app.mount(root)
16
+ return {unmount() {app.unmount(); if(!hadRootClass) root.classList.remove('dsmapp-vue-root')}}
17
+ }
18
+ // Register with the existing per-package DSM window loader. Never share a
19
+ // singleton app or user/session state between different mount calls.
20
+ const script = document.currentScript as HTMLScriptElement | null
21
+ const packageID = script?.getAttribute('data-package')
22
+ const host = window as unknown as {DsmPackageApps: Record<string, {mount: typeof mount}>}
23
+ if (packageID) {
24
+ host.DsmPackageApps = host.DsmPackageApps || {}
25
+ host.DsmPackageApps[packageID] = {mount}
26
+ }
@@ -0,0 +1,14 @@
1
+ .dsmapp-vue-root {height: 100%; min-height: 0; width: 100%; overflow: hidden;}
2
+ [data-ui-app] .integration-app {padding: var(--ui-space-5, 20px); max-width: 72ch; color: var(--ui-text); font-family: var(--ui-font-sans); font-size: 13px; line-height: 1.5;}
3
+ [data-ui-app] .integration-heading {display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 20px;}
4
+ [data-ui-app] .integration-heading h1 {font-size: 20px; font-weight: 600; margin: 0;}
5
+ [data-ui-app] .integration-heading p {color: var(--ui-text); margin: 4px 0 0;}
6
+ [data-ui-app] .integration-app section {margin-top: 24px;}
7
+ [data-ui-app] .integration-status h2 {font-size: 15px; font-weight: 600; margin: 0;}
8
+ [data-ui-app] .integration-status p {margin: 6px 0;}
9
+ [data-ui-app] .integration-status dl {display: grid; grid-template-columns: max-content 1fr; gap: 4px 16px; margin: 12px 0;}
10
+ [data-ui-app] .integration-status dt, [data-ui-app] .integration-help {color: var(--ui-text);}
11
+ [data-ui-app] .integration-status dd {margin: 0;}
12
+ [data-ui-app] .integration-actions {display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px;}
13
+ [data-ui-app] .integration-field {display: grid; gap: 6px; margin: 12px 0;}
14
+ [data-ui-app] .integration-notice {color: var(--ui-text);}
@@ -0,0 +1,148 @@
1
+ // @vitest-environment jsdom
2
+ import {expect, test, vi} from 'vitest'
3
+ import {nextTick} from 'vue'
4
+ import {mount} from '../src/main'
5
+ import type {Context, IntegrationState} from '../src/context'
6
+
7
+ function fixture(id: string, admin: boolean, state: IntegrationState = 'ready') {
8
+ const reopen: Array<() => void> = []
9
+ const call = vi.fn(async (method: string) => {
10
+ if(method === 'app.status') return {package: id, version:'0.1.0-0001',user:admin?'admin':'normal1',admin}
11
+ if(method === 'integration.status') return {state,reason:'fixture',packageRunning:'unknown',scheduler:'not-probed'}
12
+ if(method === 'personal.list') return {items:[{id:`${id}-private`,name:'My sample note'}]}
13
+ if(method === 'personal.read') return {id:`${id}-private`,text:`Private ${id}`}
14
+ throw {code:'unknown_method'}
15
+ })
16
+ const ctx = {call,reopen:(fn:()=>void)=>reopen.push(fn),locale:'en',root:{state:vi.fn(async()=> 'missing'),authorize:vi.fn(async()=> 'ready'),run:vi.fn(async()=>{}),remove:vi.fn(async()=> 'missing')}} as Context
17
+ const root=document.createElement('div');document.body.append(root)
18
+ const app=mount(root,ctx)
19
+ return {root,app,ctx,call,reopen}
20
+ }
21
+ async function settle(){for(let i=0;i<12;i++){await Promise.resolve();await nextTick()}await new Promise(resolve=>setTimeout(resolve,25))}
22
+ function click(root:HTMLElement,text:string){const node=[...root.querySelectorAll('button')].find(node=>node.textContent?.trim()===text);expect(node, text).toBeTruthy();node!.click()}
23
+
24
+ test('six states and ordinary-user errors give actionable status without approval controls',async()=>{
25
+ for(const state of ['not-enabled','ready','needs-approval','incompatible','stopped','failed'] as IntegrationState[]){
26
+ const f=fixture('Ordinary',false,state);await settle()
27
+ expect(f.root.querySelector('[data-integration-state]')?.getAttribute('data-integration-state')).toBe(state)
28
+ expect(f.root.textContent).not.toContain('Approve and activate')
29
+ f.app.unmount();f.root.remove()
30
+ }
31
+ })
32
+ test('approval cancellation never runs the root task and all fixed controls recheck status',async()=>{
33
+ const f=fixture('Admin',true,'needs-approval');await settle()
34
+ f.ctx.root!.authorize=vi.fn(async()=> 'cancelled')
35
+ click(f.root,'Approve and activate');await settle()
36
+ expect(f.ctx.root!.run).not.toHaveBeenCalled()
37
+ expect(f.root.textContent).toContain('Approval cancelled')
38
+ f.ctx.root!.authorize=vi.fn(async()=> 'ready')
39
+ click(f.root,'Approve and activate');await settle()
40
+ expect(f.ctx.root!.run).toHaveBeenCalledWith('helper_enable')
41
+ for(const [label,op] of [['Recover','helper_recover'],['Teardown','helper_teardown']] as const){click(f.root,label);await settle();expect(f.ctx.root!.run).toHaveBeenCalledWith(op)}
42
+ click(f.root,'Disable');await settle();click(f.root,'Confirm disable');await settle();expect(f.ctx.root!.run).toHaveBeenCalledWith('helper_disable')
43
+ f.app.unmount();f.root.remove()
44
+ })
45
+ test('two generated package IDs keep mounts, reopen events, overlays and unmount independent',async()=>{
46
+ const a=fixture('M1bAlpha',true),b=fixture('M1bBeta',true);await settle()
47
+ expect(a.root.textContent).toContain('M1bAlpha');expect(a.root.textContent).not.toContain('M1bBeta')
48
+ expect(b.root.textContent).toContain('M1bBeta')
49
+ click(a.root,'Disable');await settle()
50
+ expect(a.root.querySelector('[role="dialog"]')).toBeTruthy()
51
+ expect(b.root.querySelector('[role="dialog"]')).toBeNull()
52
+ const ac=a.call.mock.calls.length,bc=b.call.mock.calls.length
53
+ a.reopen.forEach(fn=>fn());await settle();expect(a.call.mock.calls.length).toBeGreaterThan(ac);expect(b.call.mock.calls.length).toBe(bc)
54
+ a.app.unmount();await settle();expect(a.root.childElementCount).toBe(0)
55
+ const count=a.call.mock.calls.length;a.reopen.forEach(fn=>fn());await settle();expect(a.call.mock.calls.length).toBe(count)
56
+ b.reopen.forEach(fn=>fn());await settle();expect(b.call.mock.calls.length).toBeGreaterThan(bc)
57
+ b.app.unmount();a.root.remove();b.root.remove()
58
+ })
59
+
60
+ test('first-load errors replace loading text with an explicit retry state',async()=>{
61
+ const root=document.createElement('div');document.body.append(root)
62
+ const ctx={locale:'en',reopen(){},call:vi.fn(async()=>{throw {code:'auth_unavailable'}})} as Context
63
+ const app=mount(root,ctx);await settle()
64
+ expect(root.textContent).toContain('Status could not be loaded')
65
+ expect(root.textContent).not.toContain('Loading integration status')
66
+ app.unmount();root.remove()
67
+ })
68
+ test('a ready integration with no sample handle shows an explicit empty state',async()=>{
69
+ const f=fixture('Empty',false)
70
+ f.call.mockImplementation(async(method:string)=> {
71
+ if(method==='app.status')return {package:'Empty',version:'0.1.0',user:'normal1',admin:false}
72
+ if(method==='integration.status')return {state:'ready',packageRunning:'unknown',scheduler:'not-probed'}
73
+ if(method==='personal.list')return {items:[]}
74
+ throw {code:'unknown_method'}
75
+ })
76
+ f.reopen.forEach(fn=>fn());await settle()
77
+ // Reopen once after the initial in-flight refresh has finished.
78
+ f.reopen.forEach(fn=>fn());await settle()
79
+ expect(f.root.textContent).toContain('No sample data')
80
+ f.app.unmount();f.root.remove()
81
+ })
82
+
83
+ test('root failure diagnostics survive the required post-failure status refresh',async()=>{
84
+ const f=fixture('RootFailure',true,'needs-approval');await settle()
85
+ f.ctx.root!.run=vi.fn(async()=>{throw {code:'root_run_failed'}})
86
+ click(f.root,'Approve and activate');await settle()
87
+ expect(f.root.textContent).toContain('The approved operation failed')
88
+ expect(f.call.mock.calls.filter(args=>args[0]==='integration.status').length).toBeGreaterThan(1)
89
+ f.app.unmount();f.root.remove()
90
+ })
91
+
92
+ test('a local modal leaves the other mount interactive and preserves host styles',async()=>{
93
+ const a=fixture('M1bAlpha',true),b=fixture('M1bBeta',true)
94
+ const body=document.body.getAttribute('style'),html=document.documentElement.getAttribute('style')
95
+ try {
96
+ await settle();click(a.root,'Disable');await settle()
97
+ const dialog=a.root.querySelector('[role="dialog"]')!
98
+ expect(dialog).toBeTruthy()
99
+ expect(dialog.hasAttribute('aria-modal')).toBe(false)
100
+ expect(b.root.querySelector('.integration-app')!.closest('[inert], [aria-hidden="true"]')).toBeNull()
101
+ expect(getComputedStyle(b.root).pointerEvents).not.toBe('none')
102
+ expect(document.body.getAttribute('style')).toBe(body)
103
+ expect(document.documentElement.getAttribute('style')).toBe(html)
104
+ const before=b.call.mock.calls.length
105
+ click(b.root,'Refresh');await settle()
106
+ expect(b.call.mock.calls.length).toBeGreaterThan(before)
107
+ click(b.root,'Disable');await settle()
108
+ expect(a.root.querySelector('[role="dialog"]')).toBeTruthy()
109
+ expect(b.root.querySelector('[role="dialog"]')).toBeTruthy()
110
+ click(a.root,'Cancel');await settle()
111
+ expect(a.root.querySelector('[role="dialog"]')).toBeNull()
112
+ expect(b.root.querySelector('[role="dialog"]')).toBeTruthy()
113
+ expect(a.root.querySelector('.integration-app')!.closest('[inert], [aria-hidden="true"]')).toBeNull()
114
+ } finally {
115
+ a.app.unmount();b.app.unmount();a.root.remove();b.root.remove();await settle()
116
+ }
117
+ expect(document.body.getAttribute('style')).toBe(body)
118
+ expect(document.documentElement.getAttribute('style')).toBe(html)
119
+ })
120
+
121
+ test('repeated mounts of the same package use unique, package-prefixed ARIA references',async()=>{
122
+ const a=fixture('SamePackage',true),b=fixture('SamePackage',true)
123
+ try {
124
+ await settle();click(a.root,'Disable');click(b.root,'Disable');await settle()
125
+ const ids=[...document.querySelectorAll('[id]')].map(node=>node.id).filter(Boolean)
126
+ expect(new Set(ids).size).toBe(ids.length)
127
+ for(const f of [a,b]) {
128
+ const dialog=f.root.querySelector('[role="dialog"]')!
129
+ for(const attr of ['aria-labelledby','aria-describedby']) {
130
+ const id=dialog.getAttribute(attr)!
131
+ // CLI substitutes the package ID in this template before building.
132
+ expect(id).toContain('__PACKAGE__-')
133
+ expect(f.root.querySelector(`[id="${id}"]`)).toBeTruthy()
134
+ }
135
+ }
136
+ } finally {a.app.unmount();b.app.unmount();a.root.remove();b.root.remove();await settle()}
137
+ })
138
+
139
+ test('unmount releases its sizing class and preserves an existing host class',async()=>{
140
+ const f=fixture('Sizing',false)
141
+ expect(f.root.classList.contains('dsmapp-vue-root')).toBe(true)
142
+ f.app.unmount()
143
+ expect(f.root.classList.contains('dsmapp-vue-root')).toBe(false)
144
+ f.root.classList.add('dsmapp-vue-root')
145
+ const app=mount(f.root,f.ctx);await settle();app.unmount()
146
+ expect(f.root.classList.contains('dsmapp-vue-root')).toBe(true)
147
+ f.root.remove()
148
+ })
@@ -0,0 +1 @@
1
+ {"compilerOptions":{"target":"ES2022","module":"ESNext","moduleResolution":"Bundler","strict":true,"lib":["ES2022","DOM"],"skipLibCheck":true,"types":["vite/client"]},"include":["src/**/*.ts","src/**/*.vue","vite.config.ts"]}
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from 'vite'
2
+ import vue from '@vitejs/plugin-vue'
3
+ export default defineConfig({
4
+ plugins: [vue()],
5
+ // Library mode must replace Vue dependency env guards for the DSM browser.
6
+ define: {'process.env.NODE_ENV': JSON.stringify('production')},
7
+ build: {lib: {entry: 'src/main.ts', formats: ['iife'], name: 'DsmVueApp', fileName: () => 'app.js'}, cssCodeSplit: false, rollupOptions: {output: {assetFileNames: 'app.[ext]'}}},
8
+ })
@@ -0,0 +1,2 @@
1
+ dist/
2
+ node_modules/
@@ -1,86 +0,0 @@
1
- import { type JWK } from "jose";
2
- export declare const PLATFORM_ATTESTATION_ISSUER = "synopkgland-curated";
3
- export declare const APP_PACKAGE_ATTESTATION_TYP = "app-package-attestation";
4
- export declare const APP_PACKAGE_ATTESTATION_VERSION: 2;
5
- export declare const CURATED_RELEASE_ATTESTATION_TYP = "curated-release-attestation";
6
- export declare const CURATED_RELEASE_ATTESTATION_VERSION: 1;
7
- export interface AppPackageSigningKey {
8
- kid: string;
9
- privateKey: CryptoKey;
10
- }
11
- export interface LabAttestationKeys extends AppPackageSigningKey {
12
- publicKey: CryptoKey;
13
- publicJwk: JWK;
14
- }
15
- /** Inputs for the token embedded in the generated SPK. */
16
- export interface CuratedAppPackageAttestationInput {
17
- packageIdentity: string;
18
- packageVersion: string;
19
- packageBuild: number;
20
- contractDigest: string;
21
- bundleDigest: string;
22
- capabilities: string[];
23
- }
24
- /** Inputs for the detached token published beside the final SPK. */
25
- export interface CuratedReleaseAttestationInput {
26
- packageIdentity: string;
27
- packageVersion: string;
28
- packageBuild: number;
29
- artifactDigest: string;
30
- embeddedAttestationDigest: string;
31
- }
32
- export interface AppPackageAttestationClaims {
33
- typ: typeof APP_PACKAGE_ATTESTATION_TYP;
34
- version: typeof APP_PACKAGE_ATTESTATION_VERSION;
35
- packageIdentity: string;
36
- packageVersion: string;
37
- packageBuild: number;
38
- contractDigest: string;
39
- bundleDigest: string;
40
- capabilities: string[];
41
- issuedAt: string;
42
- }
43
- export interface CuratedReleaseAttestationClaims {
44
- typ: typeof CURATED_RELEASE_ATTESTATION_TYP;
45
- version: typeof CURATED_RELEASE_ATTESTATION_VERSION;
46
- packageIdentity: string;
47
- packageVersion: string;
48
- packageBuild: number;
49
- artifactDigest: string;
50
- embeddedAttestationDigest: string;
51
- issuedAt: string;
52
- }
53
- export declare function loadOrCreateLabAttestationKeys(filePath: string): Promise<LabAttestationKeys>;
54
- /**
55
- * Sign the local development token used by the inner-loop flow.
56
- * Curated releases must use the v2 function below and never this helper.
57
- */
58
- export declare function signAppPackageAttestationToken(input: {
59
- packageIdentity: string;
60
- contractDigest: string;
61
- bundleDigest: string;
62
- capabilities: string[];
63
- }, keys: LabAttestationKeys): Promise<{
64
- token: string;
65
- attestationDigest: string;
66
- }>;
67
- /** Sign the v2 App Package Attestation embedded in a Curated SPK. */
68
- export declare function signCuratedAppPackageAttestationToken(input: CuratedAppPackageAttestationInput, key: AppPackageSigningKey): Promise<{
69
- token: string;
70
- attestationDigest: string;
71
- claims: AppPackageAttestationClaims;
72
- }>;
73
- /** Sign the v1 detached release attestation for a final SPK archive. */
74
- export declare function signCuratedReleaseAttestationToken(input: CuratedReleaseAttestationInput, key: AppPackageSigningKey): Promise<{
75
- token: string;
76
- attestationDigest: string;
77
- claims: CuratedReleaseAttestationClaims;
78
- }>;
79
- /** Stable JSON representation used by the embedded token's jti binding. */
80
- export declare function canonicalAppPackageClaims(claims: AppPackageAttestationClaims): string;
81
- export declare function digestAppPackageClaims(claims: AppPackageAttestationClaims): string;
82
- /** Stable JSON representation used by the detached token's jti binding. */
83
- export declare function canonicalCuratedReleaseClaims(claims: CuratedReleaseAttestationClaims): string;
84
- export declare function digestCuratedReleaseClaims(claims: CuratedReleaseAttestationClaims): string;
85
- export declare function normalizeCapabilities(capabilities: string[]): string[];
86
- export declare function assertPackageVersionBinding(packageVersion: string, packageBuild: number): void;