@synopackageland/cli 0.3.10 → 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 (280) 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 +45 -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/spk-version.d.ts +2 -14
  20. package/dist/deploy/spk-version.js +25 -111
  21. package/dist/dev.d.ts +1 -3
  22. package/dist/dev.js +14 -28
  23. package/dist/error-registry.js +810 -862
  24. package/dist/host/dsm-client.js +12 -272
  25. package/dist/host/dsm-port-profile.d.ts +0 -1
  26. package/dist/host/dsm-port-profile.js +0 -2
  27. package/dist/host/types.d.ts +1 -83
  28. package/dist/index.d.ts +0 -9
  29. package/dist/index.js +0 -6
  30. package/dist/info.d.ts +0 -1
  31. package/dist/info.js +3 -10
  32. package/dist/init.d.ts +11 -1
  33. package/dist/init.js +112 -4
  34. package/dist/log.js +2 -3
  35. package/dist/native/validate.js +0 -1
  36. package/dist/standalone/binary.d.ts +9 -0
  37. package/dist/standalone/binary.js +67 -0
  38. package/dist/standalone/build.d.ts +25 -0
  39. package/dist/standalone/build.js +108 -0
  40. package/dist/standalone/dsm-window.d.ts +26 -0
  41. package/dist/standalone/dsm-window.js +251 -0
  42. package/dist/standalone/helper.d.ts +31 -0
  43. package/dist/standalone/helper.js +104 -0
  44. package/dist/standalone/i18n.d.ts +28 -0
  45. package/dist/standalone/i18n.js +161 -0
  46. package/dist/standalone/inspect-spk.d.ts +21 -0
  47. package/dist/standalone/inspect-spk.js +160 -0
  48. package/dist/standalone/manifest.d.ts +71 -0
  49. package/dist/standalone/manifest.js +276 -0
  50. package/dist/standalone/root-messages.d.ts +7 -0
  51. package/dist/standalone/root-messages.js +250 -0
  52. package/dist/standalone/root-ops.d.ts +42 -0
  53. package/dist/standalone/root-ops.js +101 -0
  54. package/dist/standalone/root-window.d.ts +35 -0
  55. package/dist/standalone/root-window.js +290 -0
  56. package/dist/standalone/spk.d.ts +22 -0
  57. package/dist/standalone/spk.js +205 -0
  58. package/dist/templates/binary/synology-app.yaml +7 -0
  59. package/dist/templates/hello-files/compose.yaml +0 -1
  60. package/dist/templates/hello-web/compose.yaml +13 -10
  61. package/dist/templates/hello-web/html/app.js +1 -23
  62. package/dist/templates/hello-web/html/index.html +1 -3
  63. package/dist/templates/hello-web-page/compose.yaml +13 -10
  64. package/dist/templates/hello-web-page/html/app.js +1 -23
  65. package/dist/templates/hello-web-page/html/index.html +1 -3
  66. package/dist/templates/native-go/README.md +268 -0
  67. package/dist/templates/native-go/backend/cmd/app/main.go +49 -0
  68. package/dist/templates/native-go/backend/cmd/helper/main.go +38 -0
  69. package/dist/templates/native-go/backend/go.mod +3 -0
  70. package/dist/templates/native-go/backend/internal/api/errors.go +33 -0
  71. package/dist/templates/native-go/backend/internal/api/registry.go +94 -0
  72. package/dist/templates/native-go/backend/internal/api/registry_test.go +329 -0
  73. package/dist/templates/native-go/backend/internal/api/server.go +155 -0
  74. package/dist/templates/native-go/backend/internal/app/root_none.go +9 -0
  75. package/dist/templates/native-go/backend/internal/app/routes.go +127 -0
  76. package/dist/templates/native-go/backend/internal/app/routes_test.go +167 -0
  77. package/dist/templates/native-go/backend/internal/dsm/auth.go +118 -0
  78. package/dist/templates/native-go/backend/internal/dsm/auth_test.go +139 -0
  79. package/dist/templates/native-go/backend/internal/helper/control.go +219 -0
  80. package/dist/templates/native-go/backend/internal/helper/control_test.go +242 -0
  81. package/dist/templates/native-go/backend/internal/helper/peer_linux.go +44 -0
  82. package/dist/templates/native-go/backend/internal/helper/peer_other.go +7 -0
  83. package/dist/templates/native-go/backend/internal/helper/process_linux.go +167 -0
  84. package/dist/templates/native-go/backend/internal/helper/process_other.go +5 -0
  85. package/dist/templates/native-go/backend/internal/helper/server_linux.go +83 -0
  86. package/dist/templates/native-go/backend/internal/helper/status.go +271 -0
  87. package/dist/templates/native-go/backend/internal/helper/status_test.go +205 -0
  88. package/dist/templates/native-go/backend/internal/helper/supervision.go +84 -0
  89. package/dist/templates/native-go/backend/internal/helper/supervision_test.go +124 -0
  90. package/dist/templates/native-go/backend/internal/helper/supervisor_linux.go +192 -0
  91. package/dist/templates/native-go/backend/internal/helper/supervisor_linux_test.go +302 -0
  92. package/dist/templates/native-go/dsm-package.yaml +58 -0
  93. package/dist/templates/native-go/gitignore +1 -0
  94. package/dist/templates/native-go/i18n/glossary.json +662 -0
  95. package/dist/templates/native-go/i18n/messages/de.json +28 -0
  96. package/dist/templates/native-go/i18n/messages/en.json +28 -0
  97. package/dist/templates/native-go/i18n/messages/es.json +28 -0
  98. package/dist/templates/native-go/i18n/messages/fr.json +28 -0
  99. package/dist/templates/native-go/i18n/messages/it.json +28 -0
  100. package/dist/templates/native-go/i18n/messages/ja.json +28 -0
  101. package/dist/templates/native-go/i18n/messages/ko.json +28 -0
  102. package/dist/templates/native-go/i18n/messages/zh-CN.json +28 -0
  103. package/dist/templates/native-go/i18n/messages/zh-TW.json +28 -0
  104. package/dist/templates/native-go/ui/app.css +35 -0
  105. package/dist/templates/native-go/ui/app.js +94 -0
  106. package/dist/templates/native-go-better-auth/backend/internal/app/identity.go +208 -0
  107. package/dist/templates/native-go-better-auth/backend/internal/app/identity_test.go +113 -0
  108. package/dist/templates/native-go-better-auth/node/app.mjs +72 -0
  109. package/dist/templates/native-go-better-auth/node/build.mjs +2 -0
  110. package/dist/templates/native-go-better-auth/node/package-lock.json +862 -0
  111. package/dist/templates/native-go-better-auth/node/package.json +1 -0
  112. package/dist/templates/native-go-better-auth/node/test/auth.test.mjs +31 -0
  113. package/dist/templates/native-go-integration/backend/internal/app/identity_none.go +6 -0
  114. package/dist/templates/native-go-integration/backend/internal/app/personal.go +203 -0
  115. package/dist/templates/native-go-integration/backend/internal/app/personal_test.go +119 -0
  116. package/dist/templates/native-go-root/backend/internal/app/root.go +107 -0
  117. package/dist/templates/native-go-root/backend/internal/app/root_test.go +124 -0
  118. package/dist/templates/native-go-root/root/whoami.sh +6 -0
  119. package/dist/templates/native-go-vue/README.md +110 -0
  120. package/dist/templates/native-go-vue/frontend/DESIGN.md +78 -0
  121. package/dist/templates/native-go-vue/frontend/package-lock.json +2507 -0
  122. package/dist/templates/native-go-vue/frontend/package.json +6 -0
  123. package/dist/templates/native-go-vue/frontend/src/App.vue +132 -0
  124. package/dist/templates/native-go-vue/frontend/src/context.ts +17 -0
  125. package/dist/templates/native-go-vue/frontend/src/main.ts +26 -0
  126. package/dist/templates/native-go-vue/frontend/src/style.css +14 -0
  127. package/dist/templates/native-go-vue/frontend/test/apps.test.ts +148 -0
  128. package/dist/templates/native-go-vue/frontend/tsconfig.json +1 -0
  129. package/dist/templates/native-go-vue/frontend/vite.config.ts +8 -0
  130. package/dist/templates/native-go-vue/gitignore +2 -0
  131. package/dist/types.d.ts +1 -1
  132. package/dist/validate.js +11 -7
  133. package/package.json +3 -7
  134. package/schema/synology-app.schema.json +36 -37
  135. package/skills/SKILL.md +4 -48
  136. package/skills/reference/create.md +11 -83
  137. package/skills/reference/deploy.md +7 -78
  138. package/skills/reference/diagnose.md +4 -101
  139. package/skills/reference/files.md +5 -41
  140. package/skills/reference/identity.md +3 -29
  141. package/skills/reference/wrap.md +0 -2
  142. package/templates/binary/synology-app.yaml +7 -0
  143. package/templates/hello-files/compose.yaml +0 -1
  144. package/templates/hello-web/compose.yaml +13 -10
  145. package/templates/hello-web/html/app.js +1 -23
  146. package/templates/hello-web/html/index.html +1 -3
  147. package/templates/hello-web-page/compose.yaml +13 -10
  148. package/templates/hello-web-page/html/app.js +1 -23
  149. package/templates/hello-web-page/html/index.html +1 -3
  150. package/templates/native-go/README.md +268 -0
  151. package/templates/native-go/backend/cmd/app/main.go +49 -0
  152. package/templates/native-go/backend/cmd/helper/main.go +38 -0
  153. package/templates/native-go/backend/go.mod +3 -0
  154. package/templates/native-go/backend/internal/api/errors.go +33 -0
  155. package/templates/native-go/backend/internal/api/registry.go +94 -0
  156. package/templates/native-go/backend/internal/api/registry_test.go +329 -0
  157. package/templates/native-go/backend/internal/api/server.go +155 -0
  158. package/templates/native-go/backend/internal/app/root_none.go +9 -0
  159. package/templates/native-go/backend/internal/app/routes.go +127 -0
  160. package/templates/native-go/backend/internal/app/routes_test.go +167 -0
  161. package/templates/native-go/backend/internal/dsm/auth.go +118 -0
  162. package/templates/native-go/backend/internal/dsm/auth_test.go +139 -0
  163. package/templates/native-go/backend/internal/helper/control.go +219 -0
  164. package/templates/native-go/backend/internal/helper/control_test.go +242 -0
  165. package/templates/native-go/backend/internal/helper/peer_linux.go +44 -0
  166. package/templates/native-go/backend/internal/helper/peer_other.go +7 -0
  167. package/templates/native-go/backend/internal/helper/process_linux.go +167 -0
  168. package/templates/native-go/backend/internal/helper/process_other.go +5 -0
  169. package/templates/native-go/backend/internal/helper/server_linux.go +83 -0
  170. package/templates/native-go/backend/internal/helper/status.go +271 -0
  171. package/templates/native-go/backend/internal/helper/status_test.go +205 -0
  172. package/templates/native-go/backend/internal/helper/supervision.go +84 -0
  173. package/templates/native-go/backend/internal/helper/supervision_test.go +124 -0
  174. package/templates/native-go/backend/internal/helper/supervisor_linux.go +192 -0
  175. package/templates/native-go/backend/internal/helper/supervisor_linux_test.go +302 -0
  176. package/templates/native-go/dsm-package.yaml +58 -0
  177. package/templates/native-go/gitignore +1 -0
  178. package/templates/native-go/i18n/glossary.json +662 -0
  179. package/templates/native-go/i18n/messages/de.json +28 -0
  180. package/templates/native-go/i18n/messages/en.json +28 -0
  181. package/templates/native-go/i18n/messages/es.json +28 -0
  182. package/templates/native-go/i18n/messages/fr.json +28 -0
  183. package/templates/native-go/i18n/messages/it.json +28 -0
  184. package/templates/native-go/i18n/messages/ja.json +28 -0
  185. package/templates/native-go/i18n/messages/ko.json +28 -0
  186. package/templates/native-go/i18n/messages/zh-CN.json +28 -0
  187. package/templates/native-go/i18n/messages/zh-TW.json +28 -0
  188. package/templates/native-go/ui/app.css +35 -0
  189. package/templates/native-go/ui/app.js +94 -0
  190. package/templates/native-go-better-auth/backend/internal/app/identity.go +208 -0
  191. package/templates/native-go-better-auth/backend/internal/app/identity_test.go +113 -0
  192. package/templates/native-go-better-auth/node/app.mjs +72 -0
  193. package/templates/native-go-better-auth/node/build.mjs +2 -0
  194. package/templates/native-go-better-auth/node/package-lock.json +862 -0
  195. package/templates/native-go-better-auth/node/package.json +1 -0
  196. package/templates/native-go-better-auth/node/test/auth.test.mjs +31 -0
  197. package/templates/native-go-integration/backend/internal/app/identity_none.go +6 -0
  198. package/templates/native-go-integration/backend/internal/app/personal.go +203 -0
  199. package/templates/native-go-integration/backend/internal/app/personal_test.go +119 -0
  200. package/templates/native-go-root/backend/internal/app/root.go +107 -0
  201. package/templates/native-go-root/backend/internal/app/root_test.go +124 -0
  202. package/templates/native-go-root/root/whoami.sh +6 -0
  203. package/templates/native-go-vue/README.md +110 -0
  204. package/templates/native-go-vue/frontend/DESIGN.md +78 -0
  205. package/templates/native-go-vue/frontend/package-lock.json +2507 -0
  206. package/templates/native-go-vue/frontend/package.json +6 -0
  207. package/templates/native-go-vue/frontend/src/App.vue +132 -0
  208. package/templates/native-go-vue/frontend/src/context.ts +17 -0
  209. package/templates/native-go-vue/frontend/src/main.ts +26 -0
  210. package/templates/native-go-vue/frontend/src/style.css +14 -0
  211. package/templates/native-go-vue/frontend/test/apps.test.ts +148 -0
  212. package/templates/native-go-vue/frontend/tsconfig.json +1 -0
  213. package/templates/native-go-vue/frontend/vite.config.ts +8 -0
  214. package/templates/native-go-vue/gitignore +2 -0
  215. package/dist/attestation/app-package.d.ts +0 -86
  216. package/dist/attestation/app-package.js +0 -206
  217. package/dist/attestation/install-on-host.d.ts +0 -14
  218. package/dist/attestation/install-on-host.js +0 -52
  219. package/dist/attestation/keyring.d.ts +0 -21
  220. package/dist/attestation/keyring.js +0 -168
  221. package/dist/attestation/platform-service.d.ts +0 -70
  222. package/dist/attestation/platform-service.js +0 -250
  223. package/dist/build-spk/broker-glue.d.ts +0 -16
  224. package/dist/build-spk/broker-glue.js +0 -40
  225. package/dist/deploy/catalog-publish.d.ts +0 -3
  226. package/dist/deploy/catalog-publish.js +0 -7
  227. package/dist/doctor.d.ts +0 -22
  228. package/dist/doctor.js +0 -822
  229. package/dist/error-collector.d.ts +0 -2
  230. package/dist/error-collector.js +0 -46
  231. package/dist/host/broker-paths.d.ts +0 -1
  232. package/dist/host/broker-paths.js +0 -1
  233. package/dist/host/broker-upgrade-preflight.d.ts +0 -61
  234. package/dist/host/broker-upgrade-preflight.js +0 -156
  235. package/dist/host/digests.d.ts +0 -5
  236. package/dist/host/digests.js +0 -63
  237. package/dist/host/fake-nas.d.ts +0 -49
  238. package/dist/host/fake-nas.js +0 -193
  239. package/dist/mock-broker/bootstrap.d.ts +0 -2
  240. package/dist/mock-broker/bootstrap.js +0 -8
  241. package/dist/mock-broker/files.d.ts +0 -60
  242. package/dist/mock-broker/files.js +0 -408
  243. package/dist/mock-broker/oidc.d.ts +0 -31
  244. package/dist/mock-broker/oidc.js +0 -161
  245. package/dist/mock-broker/path.d.ts +0 -9
  246. package/dist/mock-broker/path.js +0 -50
  247. package/dist/mock-broker/server.d.ts +0 -19
  248. package/dist/mock-broker/server.js +0 -350
  249. package/dist/publish/build-ledger.d.ts +0 -22
  250. package/dist/publish/build-ledger.js +0 -26
  251. package/dist/publish/community-path.d.ts +0 -6
  252. package/dist/publish/community-path.js +0 -41
  253. package/dist/publish/curated-release.d.ts +0 -78
  254. package/dist/publish/curated-release.js +0 -883
  255. package/dist/publish/file-build-ledger.d.ts +0 -18
  256. package/dist/publish/file-build-ledger.js +0 -268
  257. package/dist/publish/github-pr.d.ts +0 -25
  258. package/dist/publish/github-pr.js +0 -85
  259. package/dist/publish/github-repo.d.ts +0 -8
  260. package/dist/publish/github-repo.js +0 -21
  261. package/dist/publish/platform-service-release.d.ts +0 -60
  262. package/dist/publish/platform-service-release.js +0 -333
  263. package/dist/publish/preflight.d.ts +0 -24
  264. package/dist/publish/preflight.js +0 -138
  265. package/dist/publish/publish.d.ts +0 -27
  266. package/dist/publish/publish.js +0 -136
  267. package/dist/publish/sync-definition.d.ts +0 -4
  268. package/dist/publish/sync-definition.js +0 -48
  269. package/dist/release-bin.d.ts +0 -2
  270. package/dist/release-bin.js +0 -23
  271. package/dist/templates/hello-web/html/sdk.js +0 -68
  272. package/dist/templates/hello-web-page/html/sdk.js +0 -68
  273. package/dist/templates/native/synology-app.yaml +0 -7
  274. package/dist/test-host.d.ts +0 -15
  275. package/dist/test-host.js +0 -37
  276. package/templates/hello-web/html/sdk.js +0 -68
  277. package/templates/hello-web-page/html/sdk.js +0 -68
  278. package/templates/native/synology-app.yaml +0 -7
  279. /package/dist/templates/{native → binary}/native.yaml +0 -0
  280. /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/
package/dist/types.d.ts CHANGED
@@ -56,4 +56,4 @@ export interface ComposeDocument {
56
56
  secrets?: Record<string, unknown>;
57
57
  [key: string]: unknown;
58
58
  }
59
- export type TemplateName = "hello-web" | "hello-web-page" | "hello-files" | "compose-import" | "native";
59
+ export type TemplateName = "hello-web" | "hello-web-page" | "hello-files" | "compose-import" | "binary" | "native-go";
package/dist/validate.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { collectInstallParameters, loadContract } from "./contract/load.js";
3
+ import { contractSpan, makeError } from "./errors.js";
4
+ import { collectInstallParameters, getValuePosition, loadContract } from "./contract/load.js";
4
5
  import { validateContract } from "./contract/validate.js";
6
+ import { findListingUnsupportedInstallTypes } from "./contract/unsupported-types.js";
5
7
  import { collectComposeInterpolations, collectVolumeSourceInterpolations, loadCompose, validateAdminPortMapping, validateComposeHostBinds, validateComposeNumericCommandArgs, validateComposeRunAs, validateComposeSecurity, } from "./compose/analyze.js";
6
8
  import { loadNative } from "./native/load.js";
7
9
  import { validateNative } from "./native/validate.js";
@@ -16,6 +18,14 @@ export function validateProject(project, options = {}) {
16
18
  const nativeFile = NATIVE_FILENAME;
17
19
  const parsed = loadContract(project.contractPath);
18
20
  errors.push(...validateContract(parsed, contractFile));
21
+ if (profile === "listing") {
22
+ for (const { name, type, code } of findListingUnsupportedInstallTypes(parsed.contract)) {
23
+ const pos = getValuePosition(parsed.doc, ["install", name, "type"]) ??
24
+ getValuePosition(parsed.doc, ["install", name]) ??
25
+ { line: 1, column: 1 };
26
+ errors.push(makeError(code, contractSpan(contractFile, pos.line, pos.column, `install.${name}.type`), `Install Parameter「${name}」的 type: ${type} 只能由 synopkgland deploy 在管理員 session 取得安裝當下的 Share 清單快照;Package Center/Package Source listing 不支援。`, "請使用 synopkgland deploy --host <name>,由管理員 session 在安裝時取得 Share 清單快照;此型別不能透過 Package Center 或 Package Source listing 上架。"));
27
+ }
28
+ }
19
29
  const hasCompose = existsSync(project.composePath);
20
30
  const nativePath = project.nativePath ?? join(project.root, NATIVE_FILENAME);
21
31
  const hasNative = existsSync(nativePath);
@@ -130,12 +140,6 @@ export function validateProject(project, options = {}) {
130
140
  suggestion: `請在 compose.yaml 以 \${${name}} 引用此參數。`,
131
141
  });
132
142
  }
133
- if (param.type === "dsm_user") {
134
- // In dynamic route, dsm_user is automatically injected into Compose by generator.
135
- // If a Compose manually writes user, COMPOSE_USER_CONFLICTS_DSM_USER is emitted.
136
- // Referenced for error-registry emitter compatibility.
137
- const _unused = "INSTALL_DSM_USER_UNREFERENCED";
138
- }
139
143
  }
140
144
  const warnings = errors.filter((item) => item.severity === "warning");
141
145
  const hardErrors = errors.filter((item) => item.severity !== "warning");
package/package.json CHANGED
@@ -1,11 +1,10 @@
1
1
  {
2
2
  "name": "@synopackageland/cli",
3
- "version": "0.3.10",
3
+ "version": "0.4.0",
4
4
  "description": "Syno Package Land Developer Kit CLI",
5
5
  "type": "module",
6
6
  "bin": {
7
- "synopkgland": "./dist/bin.js",
8
- "synopkgland-release": "./dist/release-bin.js"
7
+ "synopkgland": "./dist/bin.js"
9
8
  },
10
9
  "files": [
11
10
  "dist",
@@ -14,16 +13,13 @@
14
13
  "skills"
15
14
  ],
16
15
  "scripts": {
17
- "build": "tsc && node scripts/copy-templates.mjs",
16
+ "build": "rm -rf dist && tsc && node scripts/copy-templates.mjs",
18
17
  "test": "npm run build && node --import tsx --test test/**/*.test.ts",
19
18
  "prepublishOnly": "npm run build"
20
19
  },
21
20
  "dependencies": {
22
- "@synopackageland/dev-sdk": "^0.2.0",
23
- "@synopackageland/package-source": "^0.2.1",
24
21
  "ajv": "^8.17.1",
25
22
  "commander": "^13.1.0",
26
- "jose": "^6.0.10",
27
23
  "tar": "^7.4.3",
28
24
  "yaml": "^2.7.0"
29
25
  },
@@ -59,14 +59,24 @@
59
59
  "properties": {
60
60
  "type": {
61
61
  "type": "string",
62
- "enum": ["string", "boolean", "secret", "shared_folder", "dsm_user", "homes", "posix_all_shares"]
62
+ "enum": [
63
+ "string",
64
+ "boolean",
65
+ "secret",
66
+ "shared_folder",
67
+ "dsm_user",
68
+ "homes",
69
+ "posix_all_shares"
70
+ ]
63
71
  },
64
72
  "default": {},
65
73
  "generate": {
66
74
  "type": "boolean"
67
75
  }
68
76
  },
69
- "required": ["type"]
77
+ "required": [
78
+ "type"
79
+ ]
70
80
  }
71
81
  },
72
82
  "integration": {
@@ -77,7 +87,10 @@
77
87
  "properties": {
78
88
  "launch": {
79
89
  "type": "string",
80
- "enum": ["dsm-window", "new-page"],
90
+ "enum": [
91
+ "dsm-window",
92
+ "new-page"
93
+ ],
81
94
  "description": "Web App Launch Mode. dsm-window opens a DSM Application Window at the Trusted App Entry; new-page uses Package Center Open. Omit for dsm-window. Headless apps omit this field."
82
95
  },
83
96
  "cookies": {
@@ -89,47 +102,33 @@
89
102
  }
90
103
  },
91
104
  "additionalProperties": true
92
- },
93
- "identity": {
94
- "type": "object",
95
- "additionalProperties": false,
96
- "required": ["mode"],
97
- "properties": {
98
- "mode": {
99
- "type": "string",
100
- "enum": ["broker-managed"],
101
- "description": "v1 identity mode. SDK Apps omit oidc_compat; wrap-existing may add it."
102
- },
103
- "oidc_compat": {
104
- "type": "object",
105
- "additionalProperties": false,
106
- "required": ["redirect_path"],
107
- "properties": {
108
- "redirect_path": {
109
- "type": "string",
110
- "pattern": "^/(?!/)(?!.*\\.\\.)[^\\s?#:]*$",
111
- "description": "App-owned OIDC callback path. No scheme, host, query, or '..'."
112
- }
113
- }
114
- }
115
- }
116
105
  }
117
106
  },
118
- "additionalProperties": true
119
- },
120
- "permissions": {
121
- "type": "object"
107
+ "additionalProperties": false
122
108
  },
109
+ "permissions": false,
123
110
  "privilege": {
124
111
  "type": "object",
125
112
  "additionalProperties": false,
126
113
  "properties": {
127
- "privileged": { "type": "boolean" },
128
- "host_network": { "type": "boolean" },
129
- "host_pid": { "type": "boolean" },
130
- "host_ipc": { "type": "boolean" },
131
- "devices": { "type": "boolean" },
132
- "cap_add": { "type": "boolean" }
114
+ "privileged": {
115
+ "type": "boolean"
116
+ },
117
+ "host_network": {
118
+ "type": "boolean"
119
+ },
120
+ "host_pid": {
121
+ "type": "boolean"
122
+ },
123
+ "host_ipc": {
124
+ "type": "boolean"
125
+ },
126
+ "devices": {
127
+ "type": "boolean"
128
+ },
129
+ "cap_add": {
130
+ "type": "boolean"
131
+ }
133
132
  }
134
133
  }
135
134
  },
package/skills/SKILL.md CHANGED
@@ -1,51 +1,7 @@
1
- ---
2
- name: synopkgland
3
- description: Agent Authoring for Syno Package Land — create, validate, and Inner Loop deploy a Personal App to the author's NAS with synopkgland CLI. Load when the user wants to build a DSM app from a brief, scaffold from templates, validate App Contract + Compose, deploy with Local Attestation, or diagnose analyzer/deploy/readiness failures. Not for Curated publish, SSH to NAS, or inventing platform fields.
4
- version: 0.1.0
5
- ---
1
+ # Independent Synology SPKs
6
2
 
7
- Syno Package Land Skill Pack(與 `@synopackageland/cli` 同版本、同 repo,不另發 npm)。
3
+ Use this guide to initialize, build and inspect own applications or upstream OSS as independent packages. 禁止發明已完成的登入橋接、逐使用者檔案 ACL 或新身分平台。
8
4
 
9
- ## 載入方式
5
+ Start with `synopkgland init -t native-go`, `synopkgland init --binary`, or `synopkgland init -t compose-import`. Read reference/create.md or reference/wrap.md, then reference/deploy.md. Native CGI session checks belong to the package. Container authentication belongs to upstream; fixed service credentials govern mounted data access.
10
6
 
11
- 1. 執行 `synopkgland skill path`,得到 Skill Pack 根目錄。
12
- 2. **永遠先讀**本檔 `SKILL.md`。
13
- 3. 依任務載入 `reference/*.md`(見下表)。不要一次讀完全部。
14
-
15
- | 階段 | 檔案 | 何時 |
16
- | --- | --- | --- |
17
- | 建立 | `reference/create.md` | 從零或一句話需求 |
18
- | 封裝 | `reference/wrap.md` | 已有 `compose.yaml` 或要補契約 |
19
- | 身分 | `reference/identity.md` | 需要 DSM 登入/Open |
20
- | 檔案 | `reference/files.md` | 需要 NAS 檔案 |
21
- | 部署 | `reference/deploy.md` | 要 `deploy` 到 NAS |
22
- | 排查 | `reference/diagnose.md` | `validate`/`deploy`/Open 失敗 |
23
-
24
- 也可用 `synopkgland skill list`、`synopkgland skill show <id>` 在終端機檢視。
25
-
26
- ## 權威邊界(必守)
27
-
28
- - **權威**:`synology-app.yaml` schema(`packages/app-cli/schema/synology-app.schema.json`)、`synopkgland validate`/analyzer 錯誤碼、repo 測試。Skill 只是操作介面。
29
- - **禁止發明欄位**:不得新增 schema 沒有的 top-level 或 `install` 型別;不得自創第二套 Compose/SPK DSL。不確定時先 `synopkgland inspect` 或讀模板,再 `validate`。
30
- - **禁止洩漏 secret**:不得把 DSM 密碼、signing key、API token 寫進 Git、`compose.yaml` environment、`.env` commit、或對話紀錄。NAS 憑證用 synoagentcli profile 或本機環境變數,由 CLI 讀取。
31
- - **不要讀 ADR** 當預設步驟;需要決策時 skill 才指向規格。
32
- - **不要宣稱** 15 分鐘黃金路徑已通過、公開 SDK 已 freeze、或 `inner-loop` 全綠。
33
-
34
- ## 典型流程
35
-
36
- ```text
37
- 一句話需求
38
- → reference/create.md(選模板、最少問題)
39
- → synopkgland init -t hello-web|hello-web-page|hello-files|compose-import
40
- → synopkgland dev(本機 mock)
41
- → synopkgland validate
42
- → synopkgland test
43
- → reference/deploy.md → synopkgland deploy --host <nas>
44
- → synopkgland info --host <nas>;DSM Open
45
- ```
46
-
47
- 失敗時載入 `reference/diagnose.md`;依錯誤碼修契約或 Compose,**不要猜**。
48
-
49
- ## CLI 專案根
50
-
51
- `synopkgland` 從 cwd 向上找 `synology-app.yaml`,或 `--project <dir>`。v1 不做 monorepo workspace 協定。載入 `.env` 亦從 cwd 向上找,在非專案目錄執行指令時應配合 `--project`(來源:docs/impl/STATUS.md 關鍵教訓 15)。
7
+ Never install, uninstall, publish or modify NAS data without task authorization. Prefer validate, inspect and deploy --dry-run for reviewable artifacts.