@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
@@ -1,5 +1,4 @@
1
1
  import { renderDeleteAppDataBlock } from "./delete-app-data.js";
2
- import { BROKER_INSTALLER_VARS, renderBrokerStatusProbe, } from "./broker-glue.js";
3
2
  /**
4
3
  * Escapes a literal string for safe inclusion in POSIX `/bin/sh` single quotes.
5
4
  * A single quote is represented as `'\''` (close single quote, literal escaped single quote, reopen single quote).
@@ -69,7 +68,6 @@ export function renderNativeStartStopStatus(input) {
69
68
  const envBlock = envLines.length > 0 ? `\n${envLines.join("\n")}` : "";
70
69
  const commandLine = [execToken, ...argTokens].join(" ");
71
70
  return `#!/bin/sh
72
- ${BROKER_INSTALLER_VARS}
73
71
  PKGDEST="${pkgDestFallback}"
74
72
  PKGVAR="${pkgVarFallback}"
75
73
  PIDFILE="$PKGVAR/${pkg}.pid"
@@ -129,7 +127,6 @@ case "$1" in
129
127
  exit 0
130
128
  ;;
131
129
  status)
132
- ${renderBrokerStatusProbe(pkg)}
133
130
  if pid_alive; then
134
131
  exit 0
135
132
  fi
@@ -27,7 +27,6 @@ export async function materializeNativePayload(options) {
27
27
  // The payload root IS SYNOPKG_PKGDEST (/var/packages/<pkg>/target): DSM
28
28
  // extracts package.tgz straight into it. Nesting a literal `target/`
29
29
  // directory here would put the binary at $SYNOPKG_PKGDEST/target/<exec>.
30
- // Same layout the Broker SPK ships (packages/broker/src/spk/build.ts).
31
30
  if (kind === "raw") {
32
31
  materializeRaw(sourceBytes, targetDir, rawSourceName(sourceName));
33
32
  }
package/dist/cli.js CHANGED
@@ -1,4 +1,6 @@
1
+ import { planDeployment, deployStandalone } from "./deploy/plan.js";
1
2
  import { Command } from "commander";
3
+ import { renameSync } from "node:fs";
2
4
  import { resolve, dirname } from "node:path";
3
5
  import { findProjectRoot } from "./discovery.js";
4
6
  import { loadContract } from "./contract/load.js";
@@ -14,13 +16,11 @@ import { deployProject } from "./deploy/deploy.js";
14
16
  import { createNasHost } from "./host/dsm-client.js";
15
17
  import { projectInfo, formatProjectInfo } from "./info.js";
16
18
  import { followProjectLogs } from "./log.js";
17
- import { testProjectOnHost } from "./test-host.js";
18
19
  import { formatSkillList, readSkill, resolveSkillsRoot } from "./skill.js";
19
- import { publishProject, resolveCommunityAppsRoot } from "./publish/publish.js";
20
- import { formatPreflight, runPublicReleasePreflight } from "./publish/preflight.js";
21
- import { createExecGitHubCli, createExecGitOps } from "./publish/github-pr.js";
22
- import { communityAppsGitHubRepo } from "./publish/github-repo.js";
23
20
  import { resolveInstallParameterExtraValues } from "./deploy/param-values.js";
21
+ import { findStandaloneRoot, loadStandaloneManifest } from "./standalone/manifest.js";
22
+ import { buildStandalone } from "./standalone/build.js";
23
+ import { inspectSpk } from "./standalone/inspect-spk.js";
24
24
  function collectKvOption(value, previous, flag, example) {
25
25
  const separator = value.indexOf("=");
26
26
  if (separator <= 0) {
@@ -44,6 +44,26 @@ function resolveProject(cwd, project) {
44
44
  const start = project ? resolve(cwd, project) : cwd;
45
45
  return findProjectRoot(start);
46
46
  }
47
+ function standaloneRoot(cwd, project) {
48
+ return findStandaloneRoot(project ? resolve(cwd, project) : cwd);
49
+ }
50
+ function formatSpkInspection(spk, report) {
51
+ const lines = [
52
+ `SPK:${spk}`,
53
+ `package:${report.package ?? "?"} version:${report.version ?? "?"}`,
54
+ `arch:${report.arch ?? "?"} runtime:${report.kind ?? "?"}`,
55
+ `簽署:${report.signed ? "有 Synology 簽章" : "未簽署(Package Center 需允許任何發行者)"}`,
56
+ `CGI:${report.cgi ?? "無"}`,
57
+ "payload:",
58
+ ...report.payload.map((entry) => ` ${entry.mode} ${entry.path}`),
59
+ ];
60
+ const integration = report.payload.some(entry => entry.path === "integration/helper");
61
+ lines.push(report.findings.length === 0
62
+ ? integration ? "檢查:安裝以套件帳號執行;執行期整合另需管理員核准,此檢查不證明 integration ready。" : "檢查:沒有 root 權限需求或異常。"
63
+ : "問題:");
64
+ lines.push(...report.findings.map((finding) => ` - ${finding}`));
65
+ return lines.join("\n");
66
+ }
47
67
  function requireProject(cwd, project) {
48
68
  const found = resolveProject(cwd, project);
49
69
  if (!found) {
@@ -52,6 +72,30 @@ function requireProject(cwd, project) {
52
72
  }
53
73
  return found;
54
74
  }
75
+ async function buildStandaloneCommand(root, arch, options) {
76
+ if (options.output && !arch) {
77
+ console.error("-o 只能搭配 --arch 建置單一架構;多架構請用 --out-dir。");
78
+ process.exit(1);
79
+ }
80
+ const outDir = options.output
81
+ ? dirname(resolve(process.cwd(), options.output))
82
+ : options.outDir ? resolve(process.cwd(), options.outDir) : undefined;
83
+ try {
84
+ const { outputs } = await buildStandalone({ root, arches: arch ? [arch] : undefined, outDir });
85
+ for (let output of outputs) {
86
+ if (options.output) {
87
+ const target = resolve(process.cwd(), options.output);
88
+ renameSync(output, target);
89
+ output = target;
90
+ }
91
+ console.log(`已產生 ${output}`);
92
+ }
93
+ }
94
+ catch (error) {
95
+ console.error(error instanceof Error ? error.message : String(error));
96
+ process.exit(1);
97
+ }
98
+ }
55
99
  export async function runCli(argv) {
56
100
  const program = new Command();
57
101
  program
@@ -59,51 +103,20 @@ export async function runCli(argv) {
59
103
  .description("Syno Package Land Developer Kit CLI")
60
104
  .option("--project <dir>", "App 專案根目錄");
61
105
  program
62
- .command("doctor")
63
- .description("唯讀診斷本機合約/Compose 與 NAS 部署狀態")
64
- .option("--host <name>", "synoagentcli profile 或 NAS 名稱")
65
- .option("--package <id>", "指定診斷的 Package Identity(省略時由合約判定)")
66
- .option("--dev", "診斷 Development Package Identity({package}.dev)")
67
- .option("--json", "輸出 machine-readable JSON 診斷報告")
68
- .action(async (options, command) => {
106
+ .command("validate")
107
+ .description("驗證 App Contract 與 Compose,或 Standalone Package 的 dsm-package.yaml")
108
+ .action((_options, command) => {
69
109
  const global = command.parent?.opts() ?? {};
70
- const project = resolveProject(process.cwd(), global.project);
71
- let host = null;
72
- if (options.host) {
73
- host = createNasHost({ host: options.host });
74
- if (!host) {
75
- console.error(`[${"HOST_CREDENTIALS_MISSING"}] 找不到 NAS 憑證。請設定 synoagentcli profile 或 DSM_URL/DSM_PASSWORD。`);
110
+ const standalone = standaloneRoot(process.cwd(), global.project);
111
+ if (standalone) {
112
+ const loaded = loadStandaloneManifest(standalone);
113
+ if (!loaded.ok) {
114
+ console.error(loaded.errors.map((e) => `[${e.code}] ${e.message}`).join("\n"));
76
115
  process.exit(1);
77
116
  }
117
+ console.log("驗證通過。");
118
+ return;
78
119
  }
79
- try {
80
- const { runDoctor, formatDoctorReport } = await import("./doctor.js");
81
- const report = await runDoctor({
82
- project,
83
- host,
84
- packageId: options.package,
85
- dev: options.dev,
86
- });
87
- if (options.json) {
88
- console.log(JSON.stringify(report, null, 2));
89
- }
90
- else {
91
- console.log(formatDoctorReport(report));
92
- }
93
- if (!report.ok) {
94
- process.exit(1);
95
- }
96
- }
97
- catch (error) {
98
- console.error(error instanceof Error ? error.message : String(error));
99
- process.exit(1);
100
- }
101
- });
102
- program
103
- .command("validate")
104
- .description("驗證 App Contract 與 Compose")
105
- .action((options, command) => {
106
- const global = command.parent?.opts() ?? {};
107
120
  const project = requireProject(process.cwd(), global.project);
108
121
  const result = validateProject(project, {
109
122
  profile: "personal",
@@ -119,19 +132,55 @@ export async function runCli(argv) {
119
132
  });
120
133
  program
121
134
  .command("inspect")
122
- .description("檢視 Compose 意圖與 Web entry 提示")
123
- .action((_options, command) => {
135
+ .description("檢視 Compose 意圖與 Web entry 提示;--spk 檢查已產生的 SPK")
136
+ .option("--spk <file>", "不安裝,檢查 SPK 的 identity、檔案 mode 與 root 權限需求")
137
+ .action(async (options, command) => {
124
138
  const global = command.parent?.opts() ?? {};
139
+ if (options.spk) {
140
+ const spk = resolve(process.cwd(), options.spk);
141
+ try {
142
+ const report = await inspectSpk(spk);
143
+ console.log(formatSpkInspection(spk, report));
144
+ if (report.findings.length > 0) {
145
+ process.exit(1);
146
+ }
147
+ }
148
+ catch (error) {
149
+ console.error(error instanceof Error ? error.message : String(error));
150
+ process.exit(1);
151
+ }
152
+ return;
153
+ }
125
154
  const project = requireProject(process.cwd(), global.project);
126
155
  const report = inspectProject(project);
127
156
  console.log(formatInspectReport(report));
128
157
  });
129
158
  program
130
159
  .command("init")
131
- .description("從模板建立 App 專案")
160
+ .description("從模板建立 App 專案(不覆蓋既有檔案)")
132
161
  .option("-t, --template <name>", `模板:${listTemplates().join(" | ")}`)
133
- .option("--native", "建立 Native Prebuilt Binary App 專案")
162
+ .option("--binary", "建立 Binary App:以套件自己的帳號執行預先建置的 binary(等同 -t binary)")
163
+ .option("--package <id>", "native-go:INFO.package(預設由目錄名產生,例如 photo-tagger → PhotoTagger)")
164
+ .option("--name <display>", "native-go:顯示名稱(預設由目錄名產生)")
165
+ .option("--privilege <none|root>", "native-go:root 另加由 DSM 管理員在套件視窗核准的 root 操作(預設 none)")
166
+ .option("--ui <plain|vue>", "native-go:沿用 UI 0.3.1 的 Vue 介面(預設 plain)")
167
+ .option("--integration <go|better-auth>", "native-go --ui vue:每包 helper 與私有範例;better-auth 才加入 Node")
134
168
  .argument("[dir]", "目標目錄", ".")
169
+ .addHelpText("after", `
170
+ 模板分兩類:
171
+ 獨立 Container / Binary App(synology-app.yaml):
172
+ 容器(compose.yaml):hello-web、hello-web-page、hello-files、compose-import
173
+ binary(native.yaml):執行一個預先建置的 binary 服務;可用 adminport 在自己的 port 提供網頁
174
+ Standalone Package(未簽署原生 SPK,含 Go CGI 與 DSM 視窗,dsm-package.yaml):
175
+ native-go:Go CGI backend + DSM 視窗 UI,預設需登入、admin 動作另外宣告。
176
+ --privilege root:另加固定的 root 操作(root/<名稱>.sh),由 DSM 管理員在套件視窗
177
+ 以密碼核准後才能執行;預設 none,完全不用 root。
178
+
179
+ 快速開始(native-go,需要 Go 1.24+):
180
+ synopkgland init -t native-go my-app
181
+ cd my-app && $EDITOR backend/internal/app/routes.go ui/app.js
182
+ synopkgland build-spk # → dist/<Package>-<version>-<arch>.spk
183
+ synopkgland inspect --spk dist/<file>.spk`)
135
184
  .action(async (dir, options, command) => {
136
185
  const global = command.parent?.opts() ?? {};
137
186
  if (global.project) {
@@ -139,27 +188,45 @@ export async function runCli(argv) {
139
188
  process.exit(1);
140
189
  }
141
190
  const targetDir = resolve(process.cwd(), dir);
142
- if (options.native) {
143
- initFromTemplate("native", targetDir);
144
- console.log(`已建立 native 專案於 ${targetDir}`);
145
- return;
191
+ const template = options.binary ? "binary" : options.template;
192
+ if (!template) {
193
+ console.error(`請指定 -t, --template <name> 或 --binary。可選模板:${listTemplates().join(" | ")}`);
194
+ process.exit(1);
146
195
  }
147
- if (!options.template) {
148
- console.error(`請指定 -t, --template <name> 或 --native。可選模板:${listTemplates().join(" | ")}`);
196
+ if (!isTemplateName(template)) {
197
+ console.error(`未知模板:${template}`);
149
198
  process.exit(1);
150
199
  }
151
- if (!isTemplateName(options.template)) {
152
- console.error(`未知模板:${options.template}`);
200
+ if ((options.package || options.name || options.privilege) && template !== "native-go") {
201
+ console.error("--package/--name/--privilege 只適用於 -t native-go。");
153
202
  process.exit(1);
154
203
  }
155
- initFromTemplate(options.template, targetDir);
156
- console.log(`已建立 ${options.template} 專案於 ${targetDir}`);
204
+ try {
205
+ initFromTemplate(template, targetDir, {
206
+ packageId: options.package,
207
+ displayName: options.name,
208
+ privilege: options.privilege,
209
+ ui: options.ui,
210
+ integration: options.integration,
211
+ });
212
+ }
213
+ catch (error) {
214
+ console.error(error instanceof Error ? error.message : String(error));
215
+ process.exit(1);
216
+ }
217
+ console.log(`已建立 ${template} 專案於 ${targetDir}`);
218
+ if (template === "native-go") {
219
+ console.log(`下一步:
220
+ cd ${dir}
221
+ (cd backend && go test ./...) # 認證與權限政策測試
222
+ synopkgland build-spk # 編譯 Go + 打包每個架構的未簽署 SPK
223
+ 說明見 README.md。`);
224
+ }
157
225
  });
158
226
  program
159
227
  .command("dev")
160
- .description("啟動本機 mock Broker 與 Compose")
161
- .option("--files-root <dir>", "將本機目錄對應為 NAS Logical Path")
162
- .action(async (options, command) => {
228
+ .description("啟動 App 自己的本機 Compose")
229
+ .action(async (_options, command) => {
163
230
  const global = command.parent?.opts() ?? {};
164
231
  const project = requireProject(process.cwd(), global.project);
165
232
  const validation = validateProject(project);
@@ -170,10 +237,7 @@ export async function runCli(argv) {
170
237
  try {
171
238
  const handle = await runDev({
172
239
  project,
173
- filesRoot: options.filesRoot,
174
240
  });
175
- console.log(`Mock Broker:${handle.brokerBaseUrl}`);
176
- console.log(`Bootstrap:${handle.bootstrapPath}`);
177
241
  console.log("Compose 已就緒。按 Ctrl+C 結束。");
178
242
  const shutdown = async () => {
179
243
  await handle.close();
@@ -191,31 +255,9 @@ export async function runCli(argv) {
191
255
  program
192
256
  .command("test")
193
257
  .description("驗證契約並等待 Compose 就緒")
194
- .option("--host <name>", "在已部署的 NAS 上執行 Open smoke")
195
- .option("--dev", "檢查 Development Package Identity({package}.dev)")
196
- .action(async (options, command) => {
258
+ .action(async (_options, command) => {
197
259
  const global = command.parent?.opts() ?? {};
198
260
  const project = requireProject(process.cwd(), global.project);
199
- if (options.host) {
200
- const host = createNasHost({ host: options.host });
201
- if (!host) {
202
- console.error("找不到 NAS 憑證。請設定 synoagentcli profile 或 DSM_URL/DSM_PASSWORD。");
203
- process.exit(1);
204
- }
205
- try {
206
- const result = await testProjectOnHost({ project, host, dev: options.dev });
207
- if (!result.ok) {
208
- console.error(result.message ?? "測試失敗。");
209
- process.exit(1);
210
- }
211
- console.log("NAS smoke 測試通過。");
212
- }
213
- catch (error) {
214
- console.error(error instanceof Error ? error.message : String(error));
215
- process.exit(1);
216
- }
217
- return;
218
- }
219
261
  try {
220
262
  const result = await testProject({ project });
221
263
  if (!result.ok) {
@@ -231,16 +273,40 @@ export async function runCli(argv) {
231
273
  });
232
274
  program
233
275
  .command("deploy")
234
- .description("Inner Loop Deploy 到 NAS(產生 SPK、簽發 Local Attestation、安裝)")
235
- .requiredOption("--host <name>", "synoagentcli profile 或 NAS 名稱")
276
+ .description("建置獨立 SPK 並直接安裝到 DSM")
277
+ .option("--host <name>", "synoagentcli profile 或 NAS 名稱")
236
278
  .option("--arch <family>", "native App 的目標架構:x86_64 或 aarch64(省略時由 NAS 判定)")
237
279
  .option("--dev", "部署到 Development Package Identity({package}.dev)")
238
- .option("--replace-curated", "危險:將既有 Curated App Package Attestation 降級為 Local Attestation")
239
280
  .option("--share <slot=name>", "此 NAS 上的 Shared Folder 邏輯名(契約 shared_folder 槽,可重複)", collectShareOption)
240
281
  .option("--run-as <slot=name>", "此 NAS 上的 DSM 使用者邏輯名(契約 dsm_user 槽)", collectRunAsOption)
241
282
  .option("--param <NAME=value>", "契約 string/boolean Install Parameter 答案(可重複)", collectParamOption)
283
+ .option("--dry-run", "只輸出本機部署計畫,不連接 NAS")
242
284
  .action(async (options, command) => {
243
285
  const global = command.parent?.opts() ?? {};
286
+ if (options.arch !== undefined && !isNativeArchFamily(String(options.arch))) {
287
+ throw new Error('--arch accepts x86_64 or aarch64');
288
+ }
289
+ const plan = planDeployment(resolve(process.cwd(), global.project ?? '.'), {
290
+ arch: options.arch, dev: options.dev,
291
+ });
292
+ if (options.dryRun) {
293
+ console.log(JSON.stringify(plan, null, 2));
294
+ return;
295
+ }
296
+ if (!options.host)
297
+ throw new Error('deploy requires --host, or use --dry-run');
298
+ if (plan.kind === 'native-go') {
299
+ if (options.share || options.runAs || options.param)
300
+ throw new Error('native-go does not accept container install parameters');
301
+ const host = createNasHost({ host: options.host });
302
+ if (!host)
303
+ throw new Error('NAS credentials not found');
304
+ const output = await deployStandalone(plan, host);
305
+ console.log(`已部署 ${plan.packageId}: ${output}`);
306
+ for (const note of plan.notes)
307
+ console.log(note);
308
+ return;
309
+ }
244
310
  const project = requireProject(process.cwd(), global.project);
245
311
  const parameterCheck = resolveInstallParameterExtraValues({
246
312
  contract: loadContract(project.contractPath).contract,
@@ -265,7 +331,6 @@ export async function runCli(argv) {
265
331
  project,
266
332
  host,
267
333
  dev: options.dev,
268
- replaceCurated: options.replaceCurated,
269
334
  shareValues: options.share,
270
335
  runAsValues: options.runAs,
271
336
  paramValues: options.param,
@@ -289,7 +354,7 @@ export async function runCli(argv) {
289
354
  });
290
355
  program
291
356
  .command("info")
292
- .description("顯示 NAS 上的 attestation、Open URL 與 readiness")
357
+ .description("顯示 DSM 套件狀態與 Open URL")
293
358
  .requiredOption("--host <name>", "synoagentcli profile 或 NAS 名稱")
294
359
  .option("--dev", "查詢 Development Package Identity")
295
360
  .action(async (options, command) => {
@@ -369,20 +434,27 @@ export async function runCli(argv) {
369
434
  });
370
435
  program
371
436
  .command("build-spk")
372
- .description("產生本機 thin SPK(未簽署)")
373
- .option("-o, --output <path>", "輸出 .spk 路徑")
374
- .option("--arch <family>", "native App 的目標架構:x86_64 或 aarch64(Compose App 不需要)")
437
+ .description("產生本機未簽署 SPK:容器 App(synology-app.yaml)或 Standalone Package(dsm-package.yaml,會先編譯 UI 與 Go)")
438
+ .option("-o, --output <path>", "輸出 .spk 路徑(Standalone Package 只在建置單一架構時可用)")
439
+ .option("--out-dir <dir>", "Standalone Package:輸出目錄(預設 dist/)")
440
+ .option("--arch <family>", "目標架構:x86_64 或 aarch64(native App 必填;Standalone Package 預設建置 arches 全部)")
375
441
  .action(async (options, command) => {
376
442
  const global = command.parent?.opts() ?? {};
377
- const project = requireProject(process.cwd(), global.project);
378
443
  if (options.arch !== undefined && !isNativeArchFamily(String(options.arch))) {
379
444
  console.error(`--arch 只接受 x86_64 或 aarch64,收到 ${String(options.arch)}。`);
380
445
  process.exit(1);
381
446
  }
447
+ const archFamily = options.arch ? String(options.arch) : undefined;
448
+ const standalone = standaloneRoot(process.cwd(), global.project);
449
+ if (standalone) {
450
+ await buildStandaloneCommand(standalone, archFamily, options);
451
+ return;
452
+ }
453
+ const project = requireProject(process.cwd(), global.project);
382
454
  const result = await buildSpk({
383
455
  project,
384
456
  outputPath: options.output ? resolve(process.cwd(), options.output) : undefined,
385
- archFamily: options.arch ? String(options.arch) : undefined,
457
+ archFamily,
386
458
  });
387
459
  if (!result.ok) {
388
460
  console.error(`建置失敗:${result.errors.join(", ")}`);
@@ -390,57 +462,5 @@ export async function runCli(argv) {
390
462
  }
391
463
  console.log(`已產生 ${result.outputPath}`);
392
464
  });
393
- program
394
- .command("public-release-submit")
395
- .description("送審公開發佈:先跑完本機所有 Curated 檢查,通過才對 Community Apps 建立 Pull Request。"
396
- + "不上傳 SPK、不簽署、不發佈——實際發佈由 maintainer 合併後的 trusted CI 執行。")
397
- .option("--check", "只跑本機檢查,不建立 Pull Request")
398
- .option("--base <branch>", "PR 目標分支", "master")
399
- .action(async (options, command) => {
400
- const global = command.parent?.opts() ?? {};
401
- const project = requireProject(process.cwd(), global.project);
402
- const preflight = await runPublicReleasePreflight(project);
403
- if (!preflight.ok) {
404
- // A gate's reasons belong on stderr so CI logs and `2>` capture them.
405
- console.error(formatPreflight(preflight));
406
- console.error("\n本機檢查未通過,未建立 Pull Request。");
407
- process.exit(1);
408
- }
409
- console.log(formatPreflight(preflight));
410
- if (options.check) {
411
- console.log("\n本機檢查全部通過。加上 --check 以外的執行才會送審。");
412
- return;
413
- }
414
- const communityAppsRoot = resolveCommunityAppsRoot(project.root);
415
- if (!communityAppsRoot) {
416
- console.error("送審需要 Community Apps 定義倉庫的本機副本。請先取得它,再從你的 App 專案目錄重跑:\n"
417
- + " git clone https://github.com/synopackageland/synopkgland-community-apps.git\n"
418
- + " export SYNOPKGLAND_COMMUNITY_APPS_ROOT=$PWD/synopkgland-community-apps");
419
- process.exit(1);
420
- }
421
- const repoRoot = dirname(communityAppsRoot);
422
- try {
423
- const result = await publishProject({
424
- project,
425
- communityAppsRoot,
426
- baseBranch: options.base,
427
- github: createExecGitHubCli(repoRoot, communityAppsGitHubRepo()),
428
- git: createExecGitOps(repoRoot),
429
- });
430
- if (!result.ok) {
431
- console.error(result.message ?? result.code ?? "publish 失敗。");
432
- process.exit(1);
433
- }
434
- if (result.noChanges) {
435
- console.log(result.message ?? "Community Apps 定義已是最新。");
436
- return;
437
- }
438
- console.log(`已建立 PR:${result.prUrl}`);
439
- }
440
- catch (error) {
441
- console.error(error instanceof Error ? error.message : String(error));
442
- process.exit(1);
443
- }
444
- });
445
465
  await program.parseAsync(argv);
446
466
  }
@@ -28,7 +28,7 @@ export interface ResourceWorkerEligibility {
28
28
  * - any non-headless App without a unique literal published adminport status probe
29
29
  */
30
30
  export declare function resourceWorkerEligibility(contract: AppContract, compose: ComposeDocument): ResourceWorkerEligibility;
31
- export type ComposeRoute = "static-zero-config" | "dynamic" | "broker-installer" | "reject";
31
+ export type ComposeRoute = "static-zero-config" | "dynamic" | "reject";
32
32
  export interface ComposeRouteClassification {
33
33
  route: ComposeRoute;
34
34
  eligibility: ResourceWorkerEligibility;
@@ -42,7 +42,6 @@ export interface ComposeRouteClassification {
42
42
  * placed in the project .env.
43
43
  */
44
44
  export declare function classifyComposeRoute(contract: AppContract, parsed: ParsedCompose, composeFile?: string): ComposeRouteClassification;
45
- export declare function brokerInstallerRouteWarning(compose: ComposeDocument, composeFile?: string): ValidationError;
46
45
  export declare function collectComposeInterpolations(compose: ComposeDocument): Set<string>;
47
46
  export declare function validateComposeSecurity(compose: ComposeDocument, composeFile: string, options: {
48
47
  profile: "personal" | "listing";
@@ -180,7 +180,7 @@ function hasBuildStanza(compose) {
180
180
  }
181
181
  return Object.values(compose.services).some((service) => "build" in service && service.build !== undefined);
182
182
  }
183
- const HEADLESS_BROKER_FALLBACK_REASONS = new Set([
183
+ const HEADLESS_UNSUPPORTED_REASONS = new Set([
184
184
  "HAS_COMPOSE_SECRETS_OR_CONFIGS",
185
185
  "HAS_BUILD_STANZA",
186
186
  "HAS_PRIVILEGED_CAPABILITY",
@@ -204,7 +204,7 @@ export function classifyComposeRoute(contract, parsed, composeFile = "compose.ya
204
204
  const hasHeadlessUnsupportedParameter = collectInstallParameters(contract).some(({ param }) => !HEADLESS_PACKAGE_ONLY_PARAMETER_TYPES.has(String(param.type)));
205
205
  const headlessPackageOnly = eligibility.headless &&
206
206
  !hasHeadlessUnsupportedParameter &&
207
- !eligibility.reasons.some((reason) => HEADLESS_BROKER_FALLBACK_REASONS.has(reason));
207
+ !eligibility.reasons.some((reason) => HEADLESS_UNSUPPORTED_REASONS.has(reason));
208
208
  const localRoute = contract.adminport !== undefined || headlessPackageOnly;
209
209
  const rejectErrors = localRoute
210
210
  ? collectRouteRejectErrors(contract, parsed, composeFile)
@@ -225,27 +225,11 @@ export function classifyComposeRoute(contract, parsed, composeFile = "compose.ya
225
225
  if (eligibility.eligible) {
226
226
  return { route: "static-zero-config", eligibility, errors: [] };
227
227
  }
228
- // Keep the route decision in one place. build-spk must not independently
229
- // reinterpret an ineligible Compose as D1: only a route with a resolved
230
- // published adminport or an eligible headless Compose, plus at least one
231
- // declared install parameter (or oidc_compat declaration), has a package-only materializer.
232
- // Every other buildable fallback is Broker.
233
228
  if ((eligibility.adminport !== undefined || headlessPackageOnly) &&
234
- (collectInstallParameters(contract).length > 0 || hasBrokerManagedOidc(contract))) {
229
+ (collectInstallParameters(contract).length > 0 || hasInterpolation(parsed.compose))) {
235
230
  return { route: "dynamic", eligibility, errors: [] };
236
231
  }
237
- return { route: "broker-installer", eligibility, errors: [] };
238
- }
239
- export function brokerInstallerRouteWarning(compose, composeFile = "compose.yaml") {
240
- const path = findDynamicComposePath(compose) ?? "services";
241
- return makeError("CURATED_BROKER_ROUTE_FORBIDDEN", composeSpanForPath(compose, composeFile, path), "此 Compose 會落到 Broker installer 路線;rootless Broker 不支援 Container Manager,無法作為 Curated production runtime。", "請改成零設定 resource-worker:使用 ./html bind 與預先 build 的 image;將 ${SYNOPKG_PKGVAR} 等平台 bind 改成 named volume,或改用 D1 dynamic route 的 string/boolean install 參數。", "warning");
242
- }
243
- function hasImmutableServiceImages(compose) {
244
- const services = compose.services;
245
- if (!services || Object.keys(services).length === 0) {
246
- return false;
247
- }
248
- return Object.values(services).every((service) => typeof service.image === "string" && /@sha256:[a-f0-9]{64}$/.test(service.image.trim()));
232
+ return { route: "reject", eligibility, errors: [makeError("COMPOSE_ROUTE_UNSUPPORTED", composeSpanForPath(parsed.compose, composeFile, "services"), `DSM resource worker cannot manage this Compose: ${eligibility.reasons.join(", ")}`, "Use prebuilt images, supported install parameters and DSM docker-project resources.")] };
249
233
  }
250
234
  function collectRouteRejectErrors(contract, parsed, composeFile) {
251
235
  const { compose } = parsed;
@@ -318,14 +302,10 @@ function collectDynamicRouteError(contract, parsed, composeFile) {
318
302
  param.type !== "shared_folder" &&
319
303
  param.type !== "dsm_user" &&
320
304
  param.type !== "posix_all_shares"));
321
- const brokerOidcInterpolationNames = hasBrokerManagedOidc(contract)
322
- ? BROKER_OIDC_INTERPOLATION_VARS
323
- : [];
324
305
  const supportedInterpolationNames = new Set([
325
306
  ...supportedParamNames,
326
307
  ...dsmUserDerivedNames,
327
308
  ...PLATFORM_BIND_VARS,
328
- ...brokerOidcInterpolationNames,
329
309
  ]);
330
310
  const interpolations = collectComposeInterpolations(compose);
331
311
  const hasComposeInterpolation = hasInterpolation(compose);
@@ -364,7 +344,7 @@ function collectDynamicRouteError(contract, parsed, composeFile) {
364
344
  : "請只使用 ${NAME} 形式,並在 synology-app.yaml 的 install 宣告對應的 string 或 boolean parameter。";
365
345
  return makeError("COMPOSE_DYNAMIC_ROUTE_UNSUPPORTED", composeSpanForPath(compose, composeFile, path), "Compose dynamic route 含有不在 D1/D2 白名單內的輸入(" +
366
346
  reasonText +
367
- ");這條 route 會拒收,不會靜默落回 Broker fallback。", undeclaredName
347
+ ");這條 route 會拒收,不會靜默改用其他執行方式。", undeclaredName
368
348
  ? interpolationSuggestion
369
349
  : `${interpolationSuggestion} secret/config 因 DSM worker 不讀 docker-compose.override.yml 而拒收,請移除後重新執行 validate。`);
370
350
  }
@@ -504,9 +484,6 @@ export function validateComposeSecurity(compose, composeFile, options) {
504
484
  ? route.errors.filter((error) => !HOST_BIND_ERROR_CODES.has(error.code))
505
485
  : [];
506
486
  errors.push(...routeErrors);
507
- if (route?.route === "broker-installer" && hasImmutableServiceImages(compose)) {
508
- errors.push(brokerInstallerRouteWarning(compose, composeFile));
509
- }
510
487
  const routeErrorKeys = new Set(routeErrors.map(validationErrorKey));
511
488
  if (!compose.services) {
512
489
  return errors;
@@ -613,26 +590,6 @@ function isForbiddenSocketMount(source) {
613
590
  * ${NAME} must be declared in contract.install as string or boolean.
614
591
  */
615
592
  const PLATFORM_BIND_VARS = new Set(["SYNOPKG_PKGVAR", "SYNO_APP_SECRETS_DIR"]);
616
- /**
617
- * Broker-managed OIDC compatibility injects these two names at install time.
618
- * They are allowed only for an explicitly declared oidc_compat legacy route;
619
- * the D1 package-only materializer does not write them.
620
- */
621
- const BROKER_OIDC_INTERPOLATION_VARS = new Set([
622
- "SYNOPKGLAND_OIDC_ISSUER",
623
- "SYNOPKGLAND_OIDC_CLIENT_ID",
624
- "SYNOPKGLAND_OIDC_ISSUER_HOST",
625
- ]);
626
- function hasBrokerManagedOidc(contract) {
627
- if (!contract.integration || typeof contract.integration !== "object") {
628
- return false;
629
- }
630
- const identity = contract.integration.identity;
631
- if (!identity || typeof identity !== "object") {
632
- return false;
633
- }
634
- return Boolean(identity.oidc_compat);
635
- }
636
593
  const INTERPOLATED_SOURCE = /^\$\{([A-Z0-9_]+)\}(.*)$/;
637
594
  const VALID_SUFFIX_SEGMENT = /^[A-Za-z0-9._-]+$/;
638
595
  function collectNamedVolumeNames(compose) {
@@ -0,0 +1,10 @@
1
+ import type { AppContract } from "../types.js";
2
+ /** Stable gate code for an install type that only the CLI can materialize. */
3
+ export declare const POSIX_ALL_SHARES_LISTING_UNSUPPORTED_CODE: "CONTRACT_POSIX_ALL_SHARES_LISTING_UNSUPPORTED";
4
+ export interface UnsupportedInstallType {
5
+ name: string;
6
+ type: string;
7
+ code: string;
8
+ }
9
+ export declare function findUnsupportedInstallTypes(contract: AppContract): UnsupportedInstallType[];
10
+ export declare function findListingUnsupportedInstallTypes(contract: AppContract): UnsupportedInstallType[];